Cow — Borrow When You Can, Own When You Must
Cow — Borrow When You Can, Own When You Must
Thesis
Cow<'_, str> (clone-on-write) returns a borrowed slice when no change is needed and an owned String when you must mutate or allocate. The win appears when the common path is already normalized. This chapter compares always-to_string() against Cow with black_box so LLVM cannot erase the work.
Mental model
input already good → Cow::Borrowed(&str) // no alloc
input needs change → Cow::Owned(String) // alloc once
Worked examples
Verified with rustc 1.99.1 --release.
Output (200_000 iterations, black_box on input and result):
Compiling cowdemo v0.1.0 (/home/box/sand-data/books-scout/runs/2026-10-27-oneshot/labs/rust-cow)
Running `target/release/cowdemo`
owned always, already-lower x200000: 6.124085ms sink=0
cow borrow, already-lower x200000: 3.849675ms sink=0
cow owned, mixed-case x200000: 3.966889ms sink=0
On this host the borrow path (~3.85 ms) beat always-owned (~6.12 ms) for already-lowercase input. Mixed-case Cow::Owned stayed in the same band as borrow here because the chars().any scan dominates the small string alloc.
Trap
Wrapping everything in Cow by habit. If you almost always allocate, a plain String API is clearer. Cow pays for the enum branch and lifetime plumbing.
Boring rule
Use Cow at API boundaries where callers often pass already-valid data (paths, headers, normalized IDs). Measure with black_box before celebrating.
Try this
- Return
Cow<'_, [u8]>from a decoder that sometimes copies. - Compare
String::fromvsCowin a serializer hot path. - Read
std::borrow::Cowexamples in the docs.