Zero-copy until mutation needed
Cow (Clone on Write) is a smart pointer that provides lazy cloning. It can hold either:
&T)T)Data is only cloned when you need to mutate it.
use std::borrow::Cow;
let borrowed: Cow<str> = Cow::Borrowed("hello");
let owned: Cow<str> = Cow::Owned(String::from("hello"));
// Converts to owned only when mutation needed
let mut cow = Cow::Borrowed("hello");
cow.to_mut().push_str(" world"); // Now owned!
use std::borrow::Cow;
/// Process user input - only allocate if modification needed
pub fn normalize_username(input: &str) -> Cow<str> {
// Check if already valid
if input.chars().all(|c| c.is_alphanumeric() || c == '_') {
// No changes needed - return borrowed
return Cow::Borrowed(input);
}
// Need to modify - convert to owned
let cleaned: String = input
.chars()
.filter(|c| c.is_alphanumeric() || *c == '_')
.collect();
Cow::Owned(cleaned)
}
// Usage
fn username_example() {
// Valid username - no allocation
let valid = normalize_username("alice123");
println!("Valid: {:?}", valid); // Borrowed
// Invalid username - allocates
let invalid = normalize_username("bob@#$456");
println!("Invalid: {:?}", invalid); // Owned
}
/// URL encoding - only encode if needed
pub fn maybe_encode_url(input: &str) -> Cow<str> {
if input.chars().all(|c| c.is_ascii_alphanumeric() || c == '/' || c == '-') {
Cow::Borrowed(input)
} else {
// Encode special characters
let encoded = input
.chars()
.map(|c| match c {
' ' => "%20".to_string(),
'&' => "%26".to_string(),
_ if c.is_ascii_alphanumeric() => c.to_string(),
_ => format!("%{:02X}", c as u8),
})
.collect();
Cow::Owned(encoded)
}
}
// Traditional approach - always allocates
fn traditional(input: &str) -> String {
input.to_string() // ALWAYS allocates
}
// Cow approach - allocates only when needed
fn with_cow(input: &str) -> Cow<str> {
if needs_modification(input) {
Cow::Owned(modify(input))
} else {
Cow::Borrowed(input) // Zero allocation!
}
}
use std::borrow::Cow;
use std::collections::HashMap;
pub struct Config {
values: HashMap<String, Cow<'static, str>>,
}
impl Config {
pub fn new() -> Self {
let mut values = HashMap::new();
// Default values (static strings - no allocation)
values.insert("host".to_string(), Cow::Borrowed("localhost"));
values.insert("port".to_string(), Cow::Borrowed("8080"));
values.insert("env".to_string(), Cow::Borrowed("development"));
Config { values }
}
/// Override config value (creates owned copy if needed)
pub fn set(&mut self, key: impl Into<String>, value: String) {
self.values.insert(key.into(), Cow::Owned(value));
}
/// Get config value
pub fn get(&self, key: &str) -> Option<&str> {
self.values.get(key).map(|cow| cow.as_ref())
}
/// Merge with environment variables
pub fn from_env(mut self) -> Self {
if let Ok(host) = std::env::var("APP_HOST") {
self.set("host", host); // Owned
}
// Other values stay as Borrowed (efficient!)
self
}
}
// Usage
fn config_example() {
let config = Config::new()
.from_env();
println!("Host: {}", config.get("host").unwrap());
println!("Port: {}", config.get("port").unwrap());
// Most values are still borrowed from static data
// Only overridden values are owned
}
use std::borrow::Cow;
pub struct Query<'a> {
table: Cow<'a, str>,
where_clause: Option<Cow<'a, str>>,
limit: Option<usize>,
}
impl<'a> Query<'a> {
/// Create query with borrowed table name
pub fn from_table(table: &'a str) -> Self {
Query {
table: Cow::Borrowed(table),
where_clause: None,
limit: None,
}
}
/// Create query with owned table name (computed at runtime)
pub fn from_dynamic_table(table: String) -> Self {
Query {
table: Cow::Owned(table),
where_clause: None,
limit: None,
}
}
/// Add WHERE clause (borrows if possible, owns if needed)
pub fn where_clause(mut self, clause: impl Into<Cow<'a, str>>) -> Self {
self.where_clause = Some(clause.into());
self
}
pub fn limit(mut self, n: usize) -> Self {
self.limit = Some(n);
self
}
pub fn build(&self) -> String {
let mut sql = format!("SELECT * FROM {}", self.table);
if let Some(where_clause) = &self.where_clause {
sql.push_str(" WHERE ");
sql.push_str(where_clause);
}
if let Some(limit) = self.limit {
sql.push_str(&format!(" LIMIT {}", limit));
}
sql
}
}
// Usage
fn query_example() {
// Static table name - borrowed
let query1 = Query::from_table("users")
.where_clause("age > 18") // Also borrowed
.limit(10);
println!("Query 1: {}", query1.build());
// Dynamic table name - owned
let table_name = format!("users_{}", "2024");
let query2 = Query::from_dynamic_table(table_name)
.where_clause(format!("created_at > '{}'", "2024-01-01")) // Owned
.limit(5);
println!("Query 2: {}", query2.build());
}
let mut cow = Cow::Borrowed("hello");
// First call clones if borrowed
let s = cow.to_mut();
s.push_str(" world");
// Now cow is Owned
assert!(matches!(cow, Cow::Owned(_)));
let cow = Cow::Borrowed("hello");
let owned: String = cow.into_owned(); // Clones if borrowed
let cow: Cow<str> = Cow::Borrowed("hello");
// Can use like &str
fn print_str(s: &str) {
println!("{}", s);
}
print_str(&cow); // Works!
/// Pattern 1: Return Cow when modification is conditional
fn process_data(input: &str, uppercase: bool) -> Cow<str> {
if uppercase {
Cow::Owned(input.to_uppercase())
} else {
Cow::Borrowed(input)
}
}
/// Pattern 2: Accept Cow to allow both borrowed and owned
fn log_message(msg: Cow<str>) {
println!("LOG: {}", msg);
}
// Can pass borrowed or owned
log_message(Cow::Borrowed("static message"));
log_message(Cow::Owned(format!("dynamic {}", 42)));
use std::borrow::Cow;
fn ensure_sorted(data: &[i32]) -> Cow<[i32]> {
if is_sorted(data) {
Cow::Borrowed(data)
} else {
let mut sorted = data.to_vec();
sorted.sort();
Cow::Owned(sorted)
}
}
fn is_sorted(data: &[i32]) -> bool {
data.windows(2).all(|w| w[0] <= w[1])
}
use std::path::{Path, PathBuf};
use std::borrow::Cow;
fn normalize_path(path: &Path) -> Cow<Path> {
if path.is_absolute() {
Cow::Borrowed(path)
} else {
// Make absolute
let absolute = std::env::current_dir()
.unwrap()
.join(path);
Cow::Owned(absolute)
}
}
pub trait ToOwned {
type Owned: Borrow<Self>;
fn to_owned(&self) -> Self::Owned;
}
// Implemented for str
impl ToOwned for str {
type Owned = String;
fn to_owned(&self) -> String {
self.to_string()
}
}
// Cow uses ToOwned under the hood
Borrowed:
โโโโโโโโโโโโโโโโโโโ
โ tag: Borrowed โ
โ ptr: &T โโโโโโโโโผโโบ Stack/Static data
โ len: usize โ
โโโโโโโโโโโโโโโโโโโ
Owned:
โโโโโโโโโโโโโโโโโโโ
โ tag: Owned โ
โ ptr: *mut T โโโโโผโโบ Heap allocation
โ len: usize โ
โ cap: usize โ
โโโโโโโโโโโโโโโโโโโ
Size: size_of:: (tag + String size)
// BAD: Defeats the purpose of Cow
fn bad(input: &str) -> String {
let cow = Cow::Borrowed(input);
cow.into_owned() // Always clones!
}
// GOOD: Return Cow, let caller decide
fn good(input: &str) -> Cow<str> {
Cow::Borrowed(input) // Defers cloning
}
// BAD: Using Cow when always owned
fn always_owned() -> Cow<str> {
Cow::Owned(format!("computed value")) // Why Cow?
}
// GOOD: Just return String
fn always_owned() -> String {
format!("computed value")
}
// BAD: Lifetime issues
fn bad() -> Cow<'static, str> {
let s = String::from("temp");
Cow::Borrowed(&s) // ERROR: s doesn't live long enough
}
// GOOD: Use appropriate lifetime
fn good(s: &str) -> Cow<str> {
Cow::Borrowed(s)
}
&TCopy types don't benefit// Benchmark: Process 1000 strings, 90% don't need modification
// Approach 1: Always clone
fn always_clone(inputs: &[&str]) -> Vec<String> {
inputs.iter().map(|s| s.to_string()).collect()
}
// Time: ~50ยตs, Allocations: 1000
// Approach 2: With Cow
fn with_cow(inputs: &[&str]) -> Vec<Cow<str>> {
inputs.iter().map(|s| {
if needs_change(s) {
Cow::Owned(modify(s))
} else {
Cow::Borrowed(s)
}
}).collect()
}
// Time: ~5ยตs, Allocations: 100
// 10x faster, 90% fewer allocations!
Build a formatter that only allocates when formatting is needed.
Hints:Canonicalize paths, borrowing when already canonical.
Hints:Transform JSON, only cloning modified fields.
Hints:Uses Cow for efficient string handling during deserialization.
View on GitHubReturns Cow from replacements to avoid cloning when no match.
View on GitHubUses Cow for route parameters and request data.
View on GitHubRun this code in the official Rust Playground