🎓 A1Learner
🔍
0 of 142 lessons completed

Rust Cow: Borrow or Build

Cow is a text value that borrows the original text or owns a changed copy. Cow is short for clone on write.

Returning String copies every time

A function that returns String must build a new String for every input, even the inputs that change nothing:

fn main() {
    let inputs = ["tidy", " tidy ", "also tidy"];
    let mut copies = 0;
    for text in inputs {
        let cleaned: String = text.trim().to_string();
        copies += 1;
        println!("{}", cleaned);
    }
    println!("{} copies for {} inputs", copies, inputs.len());
}

Two variants, one type

A Cow holds either the borrowed input, untouched, or an owned String built because a change was needed:

let borrowed: Cow<str> = Cow::Borrowed("already tidy");
let owned: Cow<str> = Cow::Owned(String::from("built fresh"));

Borrow when nothing changes

exclaim only builds a new String for inputs that lack their !:

use std::borrow::Cow;
fn exclaim(input: &str) -> Cow<'_, str> {
    if input.ends_with('!') {
        Cow::Borrowed(input)
    } else {
        Cow::Owned(format!("{input}!"))
    }
}

Ask which one you got

matches! tells the variants apart, when it matters:

let a = exclaim("hello");
let b = exclaim("wow!");
println!("a = {} (borrowed: {})", a, matches!(a, Cow::Borrowed(_)));
println!("b = {} (borrowed: {})", b, matches!(b, Cow::Borrowed(_)));

into_owned pays only when borrowed

At the point a real String is required, into_owned settles the bill — for the borrowed input only:

let owned: String = exclaim("wow!").into_owned();
println!("owned = {}", owned);

to_mut copies at the first write

Reading a Cow never copies. Writing through to_mut copies at exactly that line:

let mut c = exclaim("hey!");
println!("before write, borrowed = {}", matches!(c, Cow::Borrowed(_)));
c.to_mut().push('!');
println!("after write = {} (borrowed: {})", c, matches!(c, Cow::Borrowed(_)));

Example

use std::borrow::Cow;

fn exclaim(input: &str) -> Cow<'_, str> {
    if input.ends_with('!') {
        Cow::Borrowed(input)
    } else {
        Cow::Owned(format!("{input}!"))
    }
}

fn main() {
    let a = exclaim("hello");
    let b = exclaim("wow!");
    println!("a = {} (borrowed: {})", a, matches!(a, Cow::Borrowed(_)));
    println!("b = {} (borrowed: {})", b, matches!(b, Cow::Borrowed(_)));
    let owned: String = b.into_owned();
    println!("owned = {}", owned);
    let mut c = exclaim("hey!");
    println!("before write, borrowed = {}", matches!(c, Cow::Borrowed(_)));
    c.to_mut().push('!');
    println!("after write = {} (borrowed: {})", c, matches!(c, Cow::Borrowed(_)));
    let d = exclaim("rust");
    println!("len = {} upper = {}", d.len(), d.to_uppercase());
}

Key points

  • Some functions usually return their input unchanged.
  • Return Cow from those functions.
  • Clean text is one case.
  • An existing suffix is another case.
  • Already-lowercase text is a third case.
  • Borrow those cases, and own only the exceptions.
  • A Cow appeared earlier: String::from_utf8_lossy returns one.
  • It is Borrowed when the bytes were clean, and then it allocated nothing.
  • It is Owned when the bytes were not clean, and then it holds � repairs.

In this example, exclaim lends or builds depending on its input. "hello" has no !, so the answer is Owned("hello!") — borrowed: false. "wow!" is already fine, so the answer is Borrowed, the very input — borrowed: true, and no copy was made. into_owned turns that borrowed Cow into a String, paying the one copy only now. A second Cow starts borrowed too, and to_mut().push('!') forces its copy at the write, flipping borrowed to false and giving hey!!. The last line never asks which variant it holds. len works on a Cow. to_uppercase works, too. They work exactly as on a &str. A Cow derefs to str.

Note

Cow is not a text-only trick.

Cow<[T]> wraps slices the same way.

Text is the common case: the borrow-or-build shape earns its keep most often there.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

A function returns its input unchanged 95% of the time and a fixed-up version 5% of the time. Which return type fits?