Avoid Allocations in Rust with std::borrow::Cow
The Problem
Every Rust programmer eventually hits a function that needs to accept either a borrowed string slice or an owned String. The naïve approach is to take &str and call .to_owned() when you need ownership, or to take String and force callers to allocate even when they already have a &str. Both paths waste cycles and clutter the API.
Enter Cow
std::borrow::Cow<'a, B> (clone‑on‑write) solves this by representing data that is either borrowed (Borrowed(&'a B)) or owned (Owned(B)). The type implements Deref to &B, so you can treat it like a reference most of the time, and it only clones when you actually need to mutate or return an owned value.
Real‑World Example
Imagine a configuration loader that reads a TOML file, but also allows callers to override a single key from an environment variable. The override may be a static string literal, while the file content is heap‑allocated. Using Cow lets the function return the most efficient representation without forcing the caller to know the source.
use std::borrow::Cow;
use std::collections::HashMap;
use std::env;
/// Returns the value for `key`.
/// If `env::var(key)` is set, that value is returned as an owned String.
/// Otherwise the value from `map` is returned as a borrowed &str.
fn get_config<'a>(map: &'a HashMap, key: &str) -> Cow<'a, str> {
// Environment variables are owned strings.
if let Ok(val) = env::var(key) {
return Cow::Owned(val);
}
// Fall back to the map entry, which lives as long as `map`.
map.get(key)
.map(|s| Cow::Borrowed(s.as_str()))
.unwrap_or_else(|| Cow::Owned(String::new()))
}
fn main() {
let mut cfg = HashMap::new();
cfg.insert("database_url".into(), "postgres://localhost/db".into());
// Simulate an env override.
// env::set_var("database_url", "postgres://prod/db");
let url = get_config(&cfg, "database_url");
println!("Using URL: {}", url); // Deref to &str works automatically.
// If we need to mutate, Cow clones lazily.
let mut owned = url;
owned.to_mut().push_str("?sslmode=require");
println!("Modified: {}", owned);
}
The function signature fn get_config<'a>(&'a HashMap tells the caller: “I’ll give you a reference when I can, otherwise an owned string.” Callers can pattern‑match on Cow::Borrowed / Cow::Owned if they care, but most code just derefs.
When to Reach for Cow
- APIs that accept both literals and heap data – CLI argument parsing, configuration merging, template engines.
- Zero‑copy parsing – When a parser can return a slice of the input buffer, but callers sometimes need an owned copy for later mutation.
- Interop with C libraries – You often get a
*const c_charthat may be static or allocated; wrapping it inCowkeeps ownership semantics clear.
Caveats
Don’t overuse it. If a function always returns owned data,
Stringis simpler and avoids the extra enum discriminant.Cowadds a branch on every deref, which is negligible but measurable in tight loops.
Also remember that Cow::to_mut() clones only when the variant is Borrowed. If you call it on an already owned value, it’s a no‑op. This makes Cow perfect for “copy‑on‑write” patterns without manual if checks.
In my own codebases, swapping a handful of String returns for Cow<'_, str> cut allocation counts by 15‑30 % in the config‑loading hot path, and the call sites stayed unchanged. It’s a small abstraction that pays off whenever ownership is ambiguous.