How do I work with Cow (Clone on Write) in Rust?

Walkthrough

Cow<'a, B> (Clone on Write) is a smart pointer that can hold either a borrowed reference or an owned value. It defers cloning until mutation is actually needed, optimizing performance when modifications are rare.

Key concepts:

  • Two variantsBorrowed (reference) or Owned (owned value)
  • Lazy cloning — Only clones when mutation is required
  • Zero-cost reads — Reading doesn't trigger cloning
  • Generic over ToOwned — Works with any type implementing ToOwned

When to use Cow:

  • Function parameters that might need modification
  • Returning either borrowed or owned strings
  • Optimizing when most values don't need cloning
  • Message processing where most messages are passed through
  • Configuration values that are usually defaults

Common use cases:

  • Cow<'a, str> — String data that might be borrowed or owned
  • Cow<'a, [T]> — Slice data that might be borrowed or owned
  • Cow<'a, Path> — Path data that might be borrowed or owned

Code Examples

Basic Cow Usage

use std::borrow::Cow;
 
fn process(input: Cow<'_, str>) -> Cow<'_, str> {
    if input.contains("error") {
        // Need to modify - this triggers clone
        let mut owned = input.into_owned();
        owned.push_str(" [fixed]");
        Cow::Owned(owned)
    } else {
        // No modification needed - return borrowed reference
        input
    }
}
 
fn main() {
    // Borrowed case - no allocation
    let borrowed = process(Cow::Borrowed("hello world"));
    println!("Borrowed result: {}", borrowed);
    
    // Owned case - triggers allocation
    let owned = process(Cow::Borrowed("error: something went wrong"));
    println!("Owned result: {}", owned);
}

Cow with String Processing

use std::borrow::Cow;
 
fn escape_html(input: &str) -> Cow<'_, str> {
    // Check if escaping is needed
    if input.contains('<') || input.contains('>') || input.contains('&') {
        // Need to escape - create owned string
        let escaped = input
            .replace('&', "&amp;")
            .replace('<', "&lt;")
            .replace('>', "&gt;");
        Cow::Owned(escaped)
    } else {
        // No escaping needed - return borrowed
        Cow::Borrowed(input)
    }
}
 
fn main() {
    let safe = escape_html("Hello, World!");
    println!("Safe: {} (borrowed)", safe);
    
    let unsafe_str = escape_html("<script>alert('xss')</script>");
    println!("Escaped: {} (owned)", unsafe_str);
}

Cow with Slices

use std::borrow::Cow;
 
fn filter_positive<'a>(numbers: Cow<'a, [i32]>) -> Cow<'a, [i32]> {
    // Check if any negative numbers exist
    if numbers.iter().any(|&n| n < 0) {
        // Need to filter - create owned vector
        let filtered: Vec<i32> = numbers.iter().copied().filter(|&n| n >= 0).collect();
        Cow::Owned(filtered)
    } else {
        // All positive - return borrowed
        numbers
    }
}
 
fn main() {
    let all_positive = [1, 2, 3, 4, 5];
    let result = filter_positive(Cow::Borrowed(&all_positive));
    println!("All positive: {:?}", result);
    
    let has_negative = [1, -2, 3, -4, 5];
    let result = filter_positive(Cow::Borrowed(&has_negative));
    println!("Filtered: {:?}", result);
}

Cow for Function Parameters

use std::borrow::Cow;
 
// Accept both &str and String without forcing allocation
fn greet(name: impl Into<Cow<'_, str>>) -> String {
    let name = name.into();
    format!("Hello, {}!", name)
}
 
// More explicit version
fn greet_explicit(name: Cow<'_, str>) -> String {
    format!("Hello, {}!", name)
}
 
fn main() {
    // Works with &str
    let greeting1 = greet("Alice");
    println!("{}", greeting1);
    
    // Works with String
    let greeting2 = greet(String::from("Bob"));
    println!("{}", greeting2);
    
    // Works with Cow
    let greeting3 = greet_explicit(Cow::Borrowed("Charlie"));
    println!("{}", greeting3);
}

Cow with Path

use std::borrow::Cow;
use std::path::{Path, PathBuf};
 
fn resolve_path<'a>(base: &Path, relative: Cow<'a, Path>) -> Cow<'a, Path> {
    if relative.is_absolute() {
        // Already absolute - return as-is
        relative
    } else {
        // Need to join - create owned path
        let resolved = base.join(&*relative);
        Cow::Owned(resolved)
    }
}
 
fn main() {
    let base = Path::new("/home/user");
    
    // Absolute path - returned as borrowed
    let absolute = Cow::Borrowed(Path::new("/etc/config"));
    let result = resolve_path(base, absolute);
    println!("Absolute: {:?}", result);
    
    // Relative path - needs joining
    let relative = Cow::Borrowed(Path::new("documents/file.txt"));
    let result = resolve_path(base, relative);
    println!("Resolved: {:?}", result);
}

Cow with to_mut

use std::borrow::Cow;
 
fn main() {
    let mut cow: Cow<'_, str> = Cow::Borrowed("hello");
    
    // to_mut returns &mut to owned data
    // Clones automatically if currently borrowed
    cow.to_mut().push_str(" world");
    
    println!("Modified: {}", cow);
    
    // Now cow is Owned
    match cow {
        Cow::Owned(ref s) => println!("Is owned: {}", s),
        Cow::Borrowed(s) => println!("Is borrowed: {}", s),
    }
}

Cow in Structs

use std::borrow::Cow;
 
struct Config<'a> {
    name: Cow<'a, str>,
    value: Cow<'a, str>,
}
 
impl<'a> Config<'a> {
    fn new(name: impl Into<Cow<'a, str>>, value: impl Into<Cow<'a, str>>) -> Self {
        Self {
            name: name.into(),
            value: value.into(),
        }
    }
    
    fn with_defaults() -> Self {
        Self {
            name: Cow::Borrowed("default_name"),
            value: Cow::Borrowed("default_value"),
        }
    }
}
 
fn main() {
    // Static config - uses borrowed references
    let config1 = Config::with_defaults();
    println!("Name: {}, Value: {}", config1.name, config1.value);
    
    // Dynamic config - uses owned strings
    let name = String::from("custom_name");
    let config2 = Config::new(name, "static_value");
    println!("Name: {}, Value: {}", config2.name, config2.value);
}

Cow for Caching

use std::borrow::Cow;
use std::collections::HashMap;
 
struct StringCache<'a> {
    cache: HashMap<String, Cow<'a, str>>,
}
 
impl<'a> StringCache<'a> {
    fn new() -> Self {
        Self {
            cache: HashMap::new(),
        }
    }
    
    fn get_or_compute<F>(&mut self, key: &str, compute: F) -> &str
    where
        F: FnOnce() -> String,
    {
        self.cache.entry(key.to_string()).or_insert_with(|| {
            let computed = compute();
            Cow::Owned(computed)
        });
        
        // Return reference to the cached value
        // Note: This is a simplified example
        self.cache.get(key).map(|cow| cow.as_ref()).unwrap()
    }
}
 
fn main() {
    let mut cache = StringCache::new();
    
    let value = cache.get_or_compute("greeting", || {
        println!("Computing greeting...");
        String::from("Hello, World!")
    });
    
    println!("Value: {}", value);
}

Cow with Serde

use std::borrow::Cow;
use serde::{Deserialize, Serialize};
 
#[derive(Serialize, Deserialize, Debug)]
struct Message<'a> {
    #[serde(borrow)]
    text: Cow<'a, str>,
    timestamp: u64,
}
 
fn main() {
    // Deserialization can use borrowed data
    let json = r"{"text":"Hello","timestamp":12345}";
    let msg: Message = serde_json::from_str(json).unwrap();
    println!("Message: {:?}", msg);
    
    // Can also create with owned data
    let owned_msg = Message {
        text: Cow::Owned(String::from("Owned message")),
        timestamp: 67890,
    };
    println!("Owned message: {:?}", owned_msg);
}

Cow for Zero-Copy Parsing

use std::borrow::Cow;
 
struct Header<'a> {
    key: Cow<'a, str>,
    value: Cow<'a, str>,
}
 
impl<'a> Header<'a> {
    fn parse(input: &'a str) -> Option<Self> {
        let (key, value) = input.split_once(':')?;
        Some(Self {
            key: Cow::Borrowed(key.trim()),
            value: Cow::Borrowed(value.trim()),
        })
    }
}
 
struct Request<'a> {
    headers: Vec<Header<'a>>,
}
 
impl<'a> Request<'a> {
    fn parse(input: &'a str) -> Self {
        let headers = input
            .lines()
            .filter_map(|line| Header::parse(line))
            .collect();
        Self { headers }
    }
}
 
fn main() {
    let raw_request = "Content-Type: application/json\nContent-Length: 42\n";
    let request = Request::parse(raw_request);
    
    for header in &request.headers {
        println!("{}: {}", header.key, header.value);
    }
}

Cow with into_owned

use std::borrow::Cow;
 
fn ensure_owned(input: Cow<'_, str>) -> String {
    // into_owned always returns owned data
    // Clones if currently borrowed, moves if owned
    input.into_owned()
}
 
fn main() {
    let borrowed = Cow::Borrowed("borrowed string");
    let owned = ensure_owned(borrowed);
    println!("Now owned: {}", owned);
    
    let already_owned = Cow::Owned(String::from("already owned"));
    let still_owned = ensure_owned(already_owned);
    println!("Still owned: {}", still_owned);
}

Cow Performance Comparison

use std::borrow::Cow;
 
// Without Cow - always allocates
fn process_always_clone(input: &str) -> String {
    let mut result = input.to_string();  // Always clones
    if result.contains("error") {
        result.push_str(" [fixed]");
    }
    result
}
 
// With Cow - only allocates when needed
fn process_lazy_clone(input: &str) -> Cow<'_, str> {
    if input.contains("error") {
        let mut owned = input.to_string();
        owned.push_str(" [fixed]");
        Cow::Owned(owned)
    } else {
        Cow::Borrowed(input)  // No allocation!
    }
}
 
fn main() {
    let inputs = vec![
        "hello world",
        "foo bar",
        "error: something",
        "baz qux",
    ];
    
    // Count allocations
    let mut owned_count = 0;
    for input in &inputs {
        let result = process_lazy_clone(input);
        match result {
            Cow::Owned(_) => owned_count += 1,
            Cow::Borrowed(_) => {},
        }
        println!("Result: {}", result);
    }
    
    println!("Allocations: {}/{}", owned_count, inputs.len());
}

Custom Types with Cow

use std::borrow::{Cow, ToOwned};
 
#[derive(Debug, Clone)]
struct MyData {
    values: Vec<i32>,
}
 
impl ToOwned for MyData {
    type Owned = MyData;
    
    fn to_owned(&self) -> Self::Owned {
        self.clone()
    }
}
 
fn process_data<'a>(data: Cow<'a, MyData>) -> Cow<'a, MyData> {
    if data.values.iter().any(|&v| v < 0) {
        let mut owned = data.into_owned();
        owned.values.retain(|&v| v >= 0);
        Cow::Owned(owned)
    } else {
        data
    }
}
 
fn main() {
    let data = MyData { values: vec![1, 2, 3] };
    let result = process_data(Cow::Borrowed(&data));
    println!("Result: {:?}", result);
    
    let negative_data = MyData { values: vec![1, -2, 3] };
    let result = process_data(Cow::Borrowed(&negative_data));
    println!("Filtered: {:?}", result);
}

Cow in Async Code

use std::borrow::Cow;
 
async fn process_message(msg: Cow<'_, str>) -> Cow<'_, str> {
    // Simulate async processing
    if msg.starts_with("echo:") {
        let response = format!("Echo: {}", &msg[5..]);
        Cow::Owned(response)
    } else {
        msg
    }
}
 
#[tokio::main]
async fn main() {
    let msg1 = Cow::Borrowed("echo: Hello");
    let result1 = process_message(msg1).await;
    println!("Result: {}", result1);
    
    let msg2 = Cow::Borrowed("plain message");
    let result2 = process_message(msg2).await;
    println!("Result: {}", result2);
}

Summary

Cow Variants:

Variant Description Cost
Borrowed(&'a B) Holds a reference Zero-cost
Owned(<B as ToOwned>::Owned) Holds owned value Allocation on creation

Cow Methods:

Method Description
into_owned() Returns owned value, cloning if needed
to_mut() Returns mutable reference, cloning if borrowed
is_borrowed() Returns true if borrowed variant
is_owned() Returns true if owned variant
as_ref() Returns reference to inner value

Cow Use Cases:

Scenario Benefit
Function parameters Accept both borrowed and owned
Zero-copy parsing Defer allocation until needed
Message processing Most messages pass through unchanged
Configuration Default values are static strings
HTML escaping Only allocate when escaping needed
Path resolution Only allocate for relative paths

Performance Characteristics:

Operation Borrowed Owned
Deref Cheap Cheap
into_owned() Clone Move
to_mut() Clone Cheap
as_ref() Cheap Cheap

Key Points:

  • Cow enables lazy cloning for better performance
  • Use Cow<'a, str> for strings that might be borrowed or owned
  • Use Cow<'a, [T]> for slices that might be borrowed or owned
  • to_mut() provides mutable access, cloning if necessary
  • into_owned() guarantees ownership, cloning if necessary
  • Works with any type implementing ToOwned
  • Zero-cost for read-only access to borrowed data
  • Integrates well with Serde for zero-copy deserialization
  • Prefer Cow when most data doesn't need modification