🦀 Rust - 🐮 Bovine Ergonomics
If you want to make more ergonomic programming interface, you can use cows.
Cow
keeps 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
Cow
s 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
Normally you would use Cow<T>
if:
T
is expensive to clone and want to avoid it at all costs.- Receiver sometimes wants to mutate the
T
but not always. - Caller sometimes wants access to the
T
after the call, but not always.