Telex logo Telex

Rust Patterns That Matter #11: Cow - Borrow or Own

Post 11 of 22 in Rust Patterns That Matter. Companion series: Building a Chat Server in Rust.

Previous: #10: Lifetime Annotations | Next: #12: Custom Iterators

Should your function take &str or String? If it takes &str, callers who have a String can pass a reference cheaply. But if the function sometimes needs to modify the string, it has to allocate a new String every time, even when the input passes through unchanged. Cow solves this.

The motivation

You're writing a function that escapes special characters in a string. Most strings have no special characters and pass through unchanged. Occasionally, one does, and you need to produce a modified copy.

fn escape(input: &str) -> String {
    if input.contains('&') {
        input.replace('&', "&")
    } else {
        input.to_string() // allocates even though nothing changed
    }
}

The else branch allocates a new String just to return the input unchanged. If this function is called on thousands of strings and most don't need escaping, that's thousands of unnecessary allocations.

You could return &str instead, but then you can't return the modified string - it doesn't live long enough. You need a return type that can be either a borrowed reference or an owned value.

The pattern: Cow<'a, str>

Cow stands for "clone on write." It's an enum with two variants:

enum Cow<'a, B: ToOwned> {
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}

For strings: Cow::Borrowed(&str) or Cow::Owned(String). The function returns whichever is appropriate:

use std::borrow::Cow;

fn escape<'a>(input: &'a str) -> Cow<'a, str> {
    if input.contains('&') {
        Cow::Owned(input.replace('&', "&amp;"))
    } else {
        Cow::Borrowed(input)
    }
}

When the input needs no escaping, the function returns the original reference with zero allocation. When it does need escaping, it returns the new String. Callers use the result the same way regardless of which variant it is - Cow<str> implements Deref<Target = str>, so it behaves like a &str in most contexts.

Using Cow at call sites

let clean = "hello world";
let dirty = "Tom & Jerry";

let a = escape(clean); // Cow::Borrowed — no allocation
let b = escape(dirty); // Cow::Owned — allocated

// Both work as &str:
println!("{} {}", a.len(), b.len());

// If you need a String, .into_owned() gives you one:
let s: String = a.into_owned(); // clones only if borrowed

.into_owned() returns a String. If the Cow was already Owned, it returns the String directly (no allocation). If it was Borrowed, it clones.

Beyond strings

Cow works with any type that implements ToOwned:

The pattern is the same everywhere: borrow when you can, own when you must.

Mutating through Cow

Cow has a .to_mut() method that gives you a &mut reference to the owned data. If the Cow is currently Borrowed, it clones the data into an Owned variant first - that's the "clone on write" behaviour:

let mut s: Cow<str> = Cow::Borrowed("hello");

// Triggers a clone because it's currently Borrowed
s.to_mut().push_str(" world");
// s is now Cow::Owned("hello world")

When to use it

Good uses:

When not to use it:

See it in practice: Building a Chat Server #3: Parsing and Performance uses this pattern for borrow-or-own message fields.