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('&', "&"))
} 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:
Cow<str>-&strorStringCow<[u8]>-&[u8]orVec<u8>Cow<Path>-&PathorPathBufCow<[T]>-&[T]orVec<T>
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:
- String processing that usually passes through unchanged (escaping, normalisation)
- Config values with defaults: return the default as
Borrowed, user overrides asOwned - Functions that conditionally transform input
- APIs that want to accept both
&strandStringwithout forcing either
When not to use it:
- If you always modify the input, just take
String(or&mut String) - If you never modify the input, just take
&str - If the allocation cost doesn't matter in your context, a simple
Stringreturn is clearer
See it in practice: Building a Chat Server #3: Parsing and Performance uses this pattern for borrow-or-own message fields.
Telex