Cow (Clone-on-Write)
Cow<'a, T> is either borrowed &'a T or owned T - clones on write when mutation required.
Recipe
use std::borrow::Cow;
fn maybe_upper(s: Cow<str>) -> Cow<str> {
if s.contains("urgent") {
Cow::Owned(s.to_uppercase())
} else {
s
}
}When to reach for this: APIs accepting &str or String and sometimes needing owned mutation.
Working Example
use std::borrow::Cow;
fn append_suffix<'a>(s: Cow<'a, str>, suffix: &str) -> Cow<'a, str> {
if suffix.is_empty() { return s; }
let mut owned = s.into_owned();
owned.push_str(suffix);
Cow::Owned(owned)
}
fn main() {
let borrowed = Cow::Borrowed("hello");
let owned = append_suffix(borrowed, "!");
println!("{owned}");
}What this demonstrates:
into_ownedconverts borrow to owned copy if needed- Mutation triggers allocation only when necessary
- Return
Cowavoids forcing caller to allocate
Deep Dive
Cow<str>, Cow<[u8]>, Cow<OsStr> common. Implements Deref to target.
Gotchas
- into_owned clones borrowed - Cost on first mutation. Fix: Accept cost or use
Stringupfront. - Lifetime tied to borrow - Returned
Cowmay need'staticowned branch. - Overuse Cow - Complexity vs
String/&stroverloads. Fix:impl AsRef<str>simpler sometimes. - Cow in struct - Lifetime on struct. Fix: Owned
Stringin stored structs often. - serde Cow - Deserializes to owned usually.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
&str + String overloads | Two clear APIs | Many combo APIs |
Arc<str> | Shared immutable | Single-owner mutation |
Always String | Simple | Hot path borrow |
FAQs
Cow Clone?
Clones inner if needed.to_mut?
Mut ref forcing clone if borrowed.Eq?
Compares content regardless of variant.from?
Cow::from(&str) etc.match cow?
Borrowed vs Owned arms.fn accept?
fn f(s: Cowpath API?
OsStr Cow for paths.no_std?
alloc cow.perf?
Win when rarely mutating.clippy?
unnecessary_to_owned hints.Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.