🦀 Rust - 🐮 Bovine Ergonomics
If you want to make more ergonomic programming interface, you can use cows.
Cowkeeps the user guessing if the value is to be consumed. 😋🥩
use std::borrow::Cow;
fn do_it<'a>(text: impl Into<Cow<'a, str>>) {
match text.into() {
Cow::Borrowed(s) => println!("borrowed: {}", s),
Cow::Owned(s) => println!("owned: {}", s),
}
}
// if you return cows, Rust can more easily figure out the lifetime
fn lifetime_from_parameter(input: &str) -> Cow<str> {
Cow::Borrowed(&input[0..1])
}
fn main() {
let txt = "Hello, world!".to_string();
// text: impl Into<Cow<'a, str>>
do_it("Hello, world!");
do_it(&txt);
do_it(txt);
let xxx = lifetime_from_parameter("lol");
dbg!(xxx);
// text: &str
//do_it("Hello, world!");
//do_it(&txt);
//do_it(txt.as_ref());
// text: impl Into<&'a str>
//do_it("Hello, world!");
// text: impl AsRef<str> + std::fmt::Display
//do_it("Hello, world!");
//do_it(&txt);
//do_it(txt);
}
How you would use an API like this:
- if you don't need the variable after the call, just pass the value
- if you need the variable after the call, pass a reference
This can make the API nicer to use, especially if you are not returning
Cows but just takingInto<Cow>s. Can lead to more noisy internals though.
But, it does make the API less prone to breaking changes, and more open for future optimization without user modifications.
The original use-case is optimization. ⚡️ For example, take in a string and a bovine string that might have matching content to replace or not. If there is a match, the bovine string is mutated and Cow::Owned returned but if not, just the Cow::Borrowed is returned without any extra allocation happening.
Copy-on-Write - Cow
So, usually, you would only use Cow<'_, T> to:
- Avoid Copying:
Tis expensive to clone and want to avoid it. - Mutate Conditionally: receiver wants to mutate the
Tsometimes - Keep Consist: your API is using mainly
Cows elsewhere
You get the majority of the other benefits also from
impl AsRef<str>, which also guarantees zero-cost conversion and is conceptually simpler.
fn do_stuff(text: impl AsRef<str>) {
let text = text.as_ref();
todo!()
}