Introduction
Cow<'a, T> (Clone on Write) is a smart pointer that can hold either a borrowed reference or an owned value. It delays cloning until mutation is actually needed, making it a powerful tool for functions that usually return borrowed data but occasionally need to allocate. If you have ever written a function that returns &str sometimes and String other times, Cow is the solution.
Key Concepts
Cow::Borrowed(&T): Wraps a reference. No allocation occurs.Cow::Owned(T::Owned): Wraps an owned value. Allocation has already occurred.to_mut(): Returns a mutable reference. If the Cow is currently Borrowed, it clones the data first, converting to Owned.into_owned(): Consumes the Cow and returns the owned value. Clones if Borrowed; moves if already Owned.
Real World Context
Cow is widely used in string processing, serialization libraries, and configuration parsers. For example, a URL normalization function usually returns the input unchanged (Borrowed), but occasionally must percent-encode special characters (Owned). Without Cow, you would either always allocate a new String or use complex lifetime gymnastics.
Deep Dive
The Cow enum
Under the hood, Cow is straightforward:
rustuse std::borrow::Cow; // Simplified definition: // enum Cow<'a, B: ToOwned + ?Sized> { // Borrowed(&'a B), // Owned(<B as ToOwned>::Owned), // }
For strings, Cow<'a, str> holds either &'a str (borrowed) or String (owned).
Basic usage: avoid allocation when possible
rustuse std::borrow::Cow; fn normalize_whitespace(input: &str) -> Cow<str> { if input.contains(" ") { // Multiple spaces found — must allocate a new string let cleaned = input.split_whitespace().collect::<Vec<_>>().join(" "); Cow::Owned(cleaned) } else { // Already clean — just borrow the original Cow::Borrowed(input) } } let clean = normalize_whitespace("hello world"); // Borrowed — no allocation let dirty = normalize_whitespace("hello world"); // Owned — allocated
Most strings in a typical workload are already clean, so Cow avoids allocation in the common case.
to_mut: clone on first write
The to_mut() method gives you a mutable reference, cloning only if the data is currently borrowed:
rustuse std::borrow::Cow; let mut message: Cow<str> = Cow::Borrowed("hello"); // This clones "hello" into a String, then appends message.to_mut().push_str(" world"); assert_eq!(message, "hello world"); // message is now Cow::Owned(String::from("hello world"))
If message were already Cow::Owned, to_mut() would return a mutable reference without cloning.
into_owned: extract the owned value
rustuse std::borrow::Cow; let borrowed: Cow<str> = Cow::Borrowed("hello"); let owned_string: String = borrowed.into_owned(); // Clones let already_owned: Cow<str> = Cow::Owned(String::from("world")); let moved_string: String = already_owned.into_owned(); // Moves, no clone
into_owned is useful when you need a String (or Vec<T>) and want to avoid unnecessary allocation.
Cow in struct fields
Cow works well in structs that may own or borrow their data:
rustuse std::borrow::Cow; struct LogEntry<'a> { level: &'static str, message: Cow<'a, str>, } impl<'a> LogEntry<'a> { fn info(msg: &'a str) -> Self { LogEntry { level: "INFO", message: Cow::Borrowed(msg) } } fn error(msg: String) -> Self { LogEntry { level: "ERROR", message: Cow::Owned(msg) } } }
Static log messages use Borrowed (zero cost), while dynamically constructed messages use Owned.
Common Pitfalls
- Always using
into_owned()immediately — If you always convert to an owned value right away, you lose the benefit of Cow. Let the Cow propagate through your code. - Overusing Cow for simple cases — If your function always needs to allocate, just return
String. Cow adds complexity that is only justified when borrowing is the common case. - Forgetting the lifetime parameter —
Cow<str>requires a lifetime:Cow<'a, str>orCow<'static, str>for string literals.
Best Practices
- Use Cow when most calls return borrowed data — If your function returns a modified value less than ~20% of the time, Cow saves allocations for the common path.
- Accept
impl Into<Cow<str>>for flexible APIs — This lets callers pass&str,String, orCow<str>seamlessly. - Prefer
Cow<'static, str>for error messages — Static strings are zero-cost, and dynamic formatting produces an Owned variant.
Summary
Cowdelays cloning until mutation or ownership is needed.Cow::Borrowedwraps a reference with zero allocation.to_mut()clones on first write;into_owned()extracts the owned value.- Use Cow when most calls can avoid allocation.
- Cow is widely used for string processing, configuration, and error messages.
Code Examples
use std::borrow::Cow;
// Efficient path normalization — avoids allocation for clean paths
fn normalize_path(path: &str) -> Cow<str> {
if path.contains("//") || path.contains("/./") {
let cleaned = path.replace("//", "/").replace("/./", "/");
Cow::Owned(cleaned)
} else {
Cow::Borrowed(path)
}
}
// Most paths are already clean — no allocation needed
let p1 = normalize_path("/usr/local/bin"); // Cow::Borrowed
let p2 = normalize_path("/usr//local/./bin"); // Cow::Owned
// Cow in error messages
struct AppError<'a> {
code: u16,
message: Cow<'a, str>,
}
impl AppError<'static> {
fn not_found() -> Self {
AppError { code: 404, message: Cow::Borrowed("Not found") }
}
fn custom(code: u16, msg: String) -> Self {
AppError { code, message: Cow::Owned(msg) }
}
}