Clone-on-Write (Cow)

Zero-copy until mutation needed

intermediate
cowoptimizationcloning
๐ŸŽฎ Interactive Playground

What is Cow?

Cow (Clone on Write) is a smart pointer that provides lazy cloning. It can hold either:
  • A borrowed reference (&T)
  • An owned value (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!

Real-World Example 1: String Processing (Web/Systems)

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)
    }
}

Performance Benefit:

// 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!
    }
}

Real-World Example 2: Configuration Merging (Systems)

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
}

Real-World Example 3: SQL Query Builder (Database)

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());
}

Cow Operations

to_mut() - Get Mutable Access

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(_)));

into_owned() - Convert to Owned

let cow = Cow::Borrowed("hello");
let owned: String = cow.into_owned();  // Clones if borrowed

Deref Coercion

let cow: Cow<str> = Cow::Borrowed("hello");

// Can use like &str
fn print_str(s: &str) {
    println!("{}", s);
}

print_str(&cow);  // Works!

Pattern: Cow in Function Returns

/// 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)));

Cow with Other Types

Cow<[T]> - Slices

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])
}

Cow<Path> - File Paths

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)
    }
}

Advanced Pattern: ToOwned Trait

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

Memory Layout

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::>() == 24 bytes (tag + String size)

โš ๏ธ Anti-Patterns

โš ๏ธ โŒ Mistake #1: Always Using to_owned()

// 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
}

โš ๏ธ โŒ Mistake #2: Unnecessary Cow

// 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")
}

โš ๏ธ โŒ Mistake #3: Cow with Short-Lived References

// 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)
}

When to Use Cow

โœ… Use Cow When:

  1. Conditional Modification: Often don't need to modify
  2. Static + Dynamic: Mix static and runtime strings
  3. API Flexibility: Accept both borrowed and owned
  4. Performance Critical: Want to avoid unnecessary clones
  5. Large Data: Cloning is expensive

โŒ Avoid Cow When:

  1. Always Modified: If always cloning, use owned
  2. Always Borrowed: If never cloning, use &T
  3. Small Types: Copy types don't benefit
  4. API Complexity: Adds cognitive overhead

Performance Comparison

// 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!

Exercises

Exercise 1: Smart String Formatter

Build a formatter that only allocates when formatting is needed.

Hints:
  • Check if string contains format specifiers
  • Return Borrowed if no formatting needed
  • Use Cow

Exercise 2: Path Canonicalizer

Canonicalize paths, borrowing when already canonical.

Hints:
  • Check if path needs normalization
  • Use Cow
  • Handle .. and . in paths

Exercise 3: JSON Value Transformer

Transform JSON, only cloning modified fields.

Hints:
  • Use Cow for string values
  • Only clone when transformation needed
  • Preserve structure when possible

Further Reading

Real-World Usage

๐Ÿฆ€ Serde

Uses Cow for efficient string handling during deserialization.

View on GitHub

๐Ÿฆ€ Regex

Returns Cow from replacements to avoid cloning when no match.

View on GitHub

๐Ÿฆ€ Rocket (Web Framework)

Uses Cow for route parameters and request data.

View on GitHub

๐ŸŽฎ Try it Yourself

๐ŸŽฎ

Clone-on-Write (Cow) - Playground

Run this code in the official Rust Playground