What is the Cow<'a, B> smart pointer in std::borrow and when is it useful for avoiding allocations?

Cow<'a, B> (Clone-on-Write) is a smart pointer that holds either a borrowed reference or an owned value. It enables you to write APIs that accept borrowed data when possible but can produce owned data when necessary, avoiding allocations when the borrowed form suffices. The name comes from the optimization strategy: you borrow data until you need to modify it, at which point you clone to get an owned copy.

The Core Concept

use std::borrow::Cow;
 
fn describe_cow() {
    // Cow can hold either a borrowed reference or owned data
    let borrowed: Cow<str> = Cow::Borrowed("hello");
    let owned: Cow<str> = Cow::Owned(String::from("hello"));
    
    // Both work the same way through the Cow interface
    println!("Borrowed: {}", borrowed);
    println!("Owned: {}", owned);
    
    // Cow derefs to the underlying type
    let length = borrowed.len();  // Works like &str
}

A Cow holds one of two states without requiring allocation until mutation is needed.

Basic Usage with Strings

use std::borrow::Cow;
 
fn process(input: &str) -> Cow<str> {
    if input.contains("placeholder") {
        // Need to modify - return owned String
        Cow::Owned(input.replace("placeholder", "value"))
    } else {
        // No modification needed - return borrowed reference
        Cow::Borrowed(input)
    }
}
 
fn example() {
    let s1 = "hello world";
    let s2 = "hello placeholder world";
    
    let result1 = process(s1);
    let result2 = process(s2);
    
    // result1 borrows s1 (no allocation)
    // result2 owns new String (allocated)
    
    println!("Result 1: {}", result1);
    println!("Result 2: {}", result2);
}

When the input doesn't need modification, we avoid allocation entirely.

The Type Signature

use std::borrow::Cow;
 
// Cow<'a, B> where B: ToOwned
// Common types B:
// - str (ToOwned = String)
// - [T] (ToOwned = Vec<T>)
// - Path (ToOwned = PathBuf)
// - CStr (ToOwned = CString)
// - OsStr (ToOwned = OsString)
 
fn type_examples() {
    // Cow<str> - most common for strings
    let string_cow: Cow<str> = Cow::Borrowed("hello");
    
    // Cow<[u8]> - for byte slices
    let bytes_cow: Cow<[u8]> = Cow::Borrowed(&[1, 2, 3]);
    
    // Cow<Path> - for paths
    use std::path::Path;
    let path_cow: Cow<Path> = Cow::Borrowed(Path::new("/usr/bin"));
}

Cow works with any type that implements ToOwned.

Avoiding Allocations in Functions

use std::borrow::Cow;
 
// Without Cow - always allocates
fn add_suffix_allocating(input: &str) -> String {
    let mut result = input.to_string();  // Always allocates
    result.push_str("_suffix");
    result
}
 
// With Cow - only allocates when needed
fn add_suffix_cow(input: &str) -> Cow<str> {
    if input.is_empty() {
        // Static string, no allocation
        Cow::Borrowed("_suffix")
    } else {
        // Must allocate to concatenate
        Cow::Owned(format!("{}_suffix", input))
    }
}
 
// Better: only allocate when actually modifying
fn transform_if_needed(input: &str) -> Cow<str> {
    if input.starts_with("http://") {
        // Need to transform - allocate
        Cow::Owned(input.replacen("http://", "https://", 1))
    } else {
        // No change needed - borrow
        Cow::Borrowed(input)
    }
}
 
fn comparison() {
    let url = "https://example.com";
    let http_url = "http://example.com";
    
    // No allocation for already-https URL
    let result1 = transform_if_needed(url);
    
    // Allocation for http URL
    let result2 = transform_if_needed(http_url);
    
    println!("Result 1: {}", result1);
    println!("Result 2: {}", result2);
}

The Cow version only allocates when modification is necessary.

Function Parameters Accepting Both Owned and Borrowed

use std::borrow::Cow;
 
// Accept either &str or String without forcing allocation
fn greet(name: impl Into<Cow<'static, str>>) {
    let name: Cow<str> = name.into();
    println!("Hello, {}!", name);
}
 
fn greet_flexible<'a>(name: impl Into<Cow<'a, str>>) {
    let name: Cow<str> = name.into();
    println!("Hello, {}!", name);
}
 
fn function_parameters() {
    // Can pass borrowed
    greet("Alice");
    
    // Can pass owned
    greet(String::from("Bob"));
    
    // Can pass Cow itself
    let cow: Cow<str> = Cow::Borrowed("Charlie");
    greet(cow);
}

Functions can accept both owned and borrowed data through Cow.

Clone on Write Behavior

use std::borrow::Cow;
 
fn clone_on_write() {
    let original = "hello";
    let mut cow: Cow<str> = Cow::Borrowed(original);
    
    // to_mut() returns &mut to owned data
    // If borrowed, clones first
    cow.to_mut().push_str(" world");
    
    // Now cow is Owned, original is unchanged
    println!("Original: {}", original);  // "hello"
    println!("Cow: {}", cow);             // "hello world"
    
    // to_mut() on already-owned doesn't clone
    cow.to_mut().push_str("!");
    println!("Cow: {}", cow);  // "hello world!"
}
 
fn clone_only_when_needed() {
    let original = String::from("hello");
    let mut cow: Cow<str> = Cow::Owned(original);
    
    // Already owned - no clone needed
    cow.to_mut().push_str(" world");
    
    // If we had borrowed, this would have cloned
}

The to_mut() method clones only when the Cow is currently borrowed.

Working with Byte Slices

use std::borrow::Cow;
 
fn process_bytes(input: &[u8]) -> Cow<[u8]> {
    // Check if modification is needed
    if input.contains(&b'\r') {
        // Need to remove carriage returns - allocate
        let cleaned: Vec<u8> = input.iter()
            .filter(|&&b| b != b'\r')
            .copied()
            .collect();
        Cow::Owned(cleaned)
    } else {
        // No modification needed - borrow
        Cow::Borrowed(input)
    }
}
 
fn byte_examples() {
    let clean_data = b"hello\nworld";
    let dirty_data = b"hello\r\nworld";
    
    // No allocation for clean data
    let result1 = process_bytes(clean_data);
    
    // Allocation for dirty data
    let result2 = process_bytes(dirty_data);
    
    println!("Clean result: {:?}", result1);
    println!("Dirty result: {:?}", result2);
}

Cow works with byte slices to avoid Vec allocations.

Path Handling with Cow

use std::borrow::Cow;
use std::path::{Path, PathBuf};
 
fn normalize_path(path: &Path) -> Cow<Path> {
    let path_str = path.to_string_lossy();
    
    if path_str.contains("./") || path_str.contains("../") {
        // Need to resolve - allocate
        let mut result = PathBuf::new();
        let mut components = Vec::new();
        
        for component in path.components() {
            match component.as_os_str().to_str() {
                Some(".") => {}  // Skip
                Some("..") => { components.pop(); }
                _ => components.push(component.as_os_str()),
            }
        }
        
        for c in components {
            result.push(c);
        }
        
        Cow::Owned(result)
    } else {
        // Already normalized - borrow
        Cow::Borrowed(path)
    }
}
 
fn path_examples() {
    let simple = Path::new("/usr/bin");
    let relative = Path::new("/usr/local/../bin");
    
    let result1 = normalize_path(simple);  // Borrowed
    let result2 = normalize_path(relative); // Owned
    
    println!("Simple: {:?}", result1);
    println!("Relative: {:?}", result2);
}

Path normalization often benefits from Cow's allocation avoidance.

Return Type Optimization

use std::borrow::Cow;
 
// API that returns Cow allows caller to decide on allocation
fn get_config_value(key: &str) -> Cow<str> {
    // Simulated config lookup
    match key {
        "default_timeout" => Cow::Borrowed("30"),  // Static - no allocation
        "app_name" => Cow::Borrowed("MyApp"),      // Static - no allocation
        "computed_path" => {
            // Dynamic - must allocate
            Cow::Owned(format!("/var/lib/{}/data", "myapp"))
        }
        _ => Cow::Borrowed(""),  // Default - no allocation
    }
}
 
fn return_optimization() {
    let timeout = get_config_value("default_timeout");
    let path = get_config_value("computed_path");
    
    // timeout is borrowed (static string)
    // path is owned (computed at runtime)
    
    println!("Timeout: {}", timeout);
    println!("Path: {}", path);
}

Functions can return static strings without allocation.

Into Owned

use std::borrow::Cow;
 
fn into_owned_example() {
    let borrowed: Cow<str> = Cow::Borrowed("hello");
    let owned: Cow<str> = Cow::Owned(String::from("world"));
    
    // into_owned() always produces owned data
    let s1: String = borrowed.into_owned();  // Clones from borrowed
    let s2: String = owned.into_owned();      // Takes ownership
    
    println!("s1: {}", s1);
    println!("s2: {}", s2);
}
 
// Useful when you need to guarantee ownership
fn process_and_own(input: Cow<str>) -> String {
    // Do some processing
    let processed = if input.starts_with("prefix:") {
        Cow::Owned(input[7..].to_string())
    } else {
        input
    };
    
    // Ensure we own the result
    processed.into_owned()
}

into_owned() converts either variant to an owned value.

Error Message Optimization

use std::borrow::Cow;
 
fn validate_username(username: &str) -> Result<(), Cow<str>> {
    if username.is_empty() {
        // Static error - no allocation
        Err(Cow::Borrowed("Username cannot be empty"))
    } else if username.len() < 3 {
        // Dynamic error - must allocate
        Err(Cow::Owned(format!(
            "Username '{}' is too short (minimum 3 characters)",
            username
        )))
    } else if username.contains(char::is_whitespace) {
        // Static error - no allocation
        Err(Cow::Borrowed("Username cannot contain whitespace"))
    } else {
        Ok(())
    }
}
 
fn error_examples() {
    match validate_username("") {
        Ok(()) => println!("Valid"),
        Err(e) => println!("Error: {}", e),  // No allocation
    }
    
    match validate_username("ab") {
        Ok(()) => println!("Valid"),
        Err(e) => println!("Error: {}", e),  // Allocated
    }
}

Error messages often mix static and dynamic content, making Cow ideal.

Chaining Operations

use std::borrow::Cow;
 
fn chain_operations(input: &str) -> Cow<str> {
    let result = Cow::Borrowed(input);
    
    // Chain operations - only allocates when needed
    let result = trim_if_needed(result);
    let result = normalize_whitespace(result);
    let result = add_prefix_if_missing(result, "item:");
    
    result
}
 
fn trim_if_needed(input: Cow<str>) -> Cow<str> {
    let trimmed = input.trim();
    if trimmed.len() != input.len() {
        Cow::Owned(trimmed.to_string())
    } else {
        input  // Already trimmed, no new allocation
    }
}
 
fn normalize_whitespace(input: Cow<str>) -> Cow<str> {
    // Only allocate if multiple spaces exist
    if input.contains("  ") {
        let normalized: String = input.split_whitespace()
            .collect::<Vec<_>>()
            .join(" ");
        Cow::Owned(normalized)
    } else {
        input
    }
}
 
fn add_prefix_if_missing<'a>(input: Cow<'a, str>, prefix: &'a str) -> Cow<'a, str> {
    if input.starts_with(prefix) {
        input
    } else {
        Cow::Owned(format!("{}{}", prefix, input))
    }
}
 
fn chaining_example() {
    let input1 = "  item:foo  ";  // Needs trim and has prefix
    let input2 = "item:bar";      // No changes needed
    
    let result1 = chain_operations(input1);  // Allocations
    let result2 = chain_operations(input2);  // No allocations
    
    println!("Result 1: {}", result1);
    println!("Result 2: {}", result2);
}

Operations can chain Cow values, accumulating changes efficiently.

Comparison with Alternatives

use std::borrow::Cow;
 
// Option 1: Always allocate
fn always_allocate(input: &str) -> String {
    let mut result = input.to_string();  // Always allocates
    if result.is_empty() {
        result.push_str("default");
    }
    result
}
 
// Option 2: Return static or dynamic with enum
#[derive(Debug)]
enum MaybeOwned<'a> {
    Static(&'static str),
    Borrowed(&'a str),
    Owned(String),
}
 
// More complex to work with
 
// Option 3: Use Cow
fn with_cow(input: &str) -> Cow<str> {
    if input.is_empty() {
        Cow::Borrowed("default")  // No allocation
    } else {
        Cow::Borrowed(input)      // No allocation
    }
}
 
fn comparison() {
    let empty = "";
    let value = "test";
    
    // always_allocate creates String for both
    let s1 = always_allocate(empty);
    let s2 = always_allocate(value);
    
    // with_cow only allocates if needed
    let c1 = with_cow(empty);
    let c2 = with_cow(value);
    
    // Both c1 and c2 are borrowed in this case
}

Cow provides a standard library solution for this common pattern.

Interoperability with Standard Traits

use std::borrow::Cow;
 
fn trait_impls() {
    let cow: Cow<str> = Cow::Borrowed("hello");
    
    // Deref to the borrowed type
    let len: usize = cow.len();        // &str methods
    let is_empty: bool = cow.is_empty();
    let chars = cow.chars();
    
    // Clone
    let cow2: Cow<str> = Cow::Owned(String::from("world"));
    let cloned = cow2.clone();          // Deep clones if Owned
    
    // Debug, Display
    println!("Display: {}", cow);
    println!("Debug: {:?}", cow);
    
    // From and Into
    let from_str: Cow<str> = "static".into();
    let from_string: Cow<str> = String::from("owned").into();
    let from_ref: Cow<str> = Cow::Borrowed("borrowed");
    
    // AsRef
    let as_ref: &str = cow.as_ref();
}
 
// Generic over anything that can become a Cow
fn accept_cow_input<'a>(input: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
    input.into()
}

Cow implements standard traits for ergonomic use.

Performance Characteristics

use std::borrow::Cow;
 
fn performance_comparison() {
    // Scenario: Transform 1000 strings, 90% don't need changes
    
    // Without Cow: 1000 allocations
    let inputs: Vec<&str> = (0..1000)
        .map(|i| if i % 10 == 0 { "needs_change" } else { "ok" })
        .collect();
    
    // Approach 1: Always allocate
    let allocated: Vec<String> = inputs.iter()
        .map(|s| {
            if s.starts_with("needs") {
                s.to_uppercase()
            } else {
                s.to_string()  // Unnecessary allocation
            }
        })
        .collect();
    
    // Approach 2: Use Cow
    let cow_results: Vec<Cow<str>> = inputs.iter()
        .map(|s| {
            if s.starts_with("needs") {
                Cow::Owned(s.to_uppercase())
            } else {
                Cow::Borrowed(*s)  // No allocation
            }
        })
        .collect();
    
    // cow_results has only ~100 allocations instead of 1000
}

Cow can significantly reduce allocations when most inputs don't need modification.

When Cow Helps

use std::borrow::Cow;
 
// Use Cow when:
// 1. You have a mix of static and dynamic data
// 2. Most inputs don't need modification
// 3. You want to defer or avoid allocations
// 4. API flexibility for callers
 
// Example 1: Static default with dynamic override
fn get_message(name: Option<&str>) -> Cow<str> {
    match name {
        Some(n) => Cow::Owned(format!("Hello, {}!", n)),
        None => Cow::Borrowed("Hello, World!"),
    }
}
 
// Example 2: Transform only when needed
fn sanitize_html(input: &str) -> Cow<str> {
    if input.contains('<') || input.contains('>') {
        Cow::Owned(input
            .replace('<', "&lt;")
            .replace('>', "&gt;"))
    } else {
        Cow::Borrowed(input)
    }
}
 
// Example 3: Caching with Cow
struct Cache {
    default: &'static str,
}
 
impl Cache {
    fn get(&self, key: &str) -> Cow<str> {
        if key == "default" {
            Cow::Borrowed(self.default)
        } else {
            // Simulated lookup
            Cow::Owned(format!("value_for_{}", key))
        }
    }
}

Cow excels in scenarios with conditional modification.

When Cow Doesn't Help

use std::borrow::Cow;
 
// Don't use Cow when:
// 1. You always need to modify
// 2. Lifetime management becomes too complex
// 3. The allocation overhead isn't worth it
 
// Always modifies - Cow doesn't help
fn always_modify(input: &str) -> String {
    // Would always be Owned anyway
    input.to_uppercase()
}
 
// Lifetime complexity
fn complex_lifetimes<'a, 'b>(a: &'a str, b: &'b str) -> Cow<'?, str> {
    // Lifetime of Cow unclear - which one?
    // Cow<'a, str> or Cow<'b, str>?
    // Often simpler to just return String
    if a.is_empty() {
        Cow::Borrowed(b)
    } else {
        Cow::Borrowed(a)
    }
}

Cow adds complexity that isn't always worthwhile.

Synthesis

Cow<'a, B> provides clone-on-write semantics that optimize for the common case where data doesn't need modification:

Key concepts:

Aspect Description
Borrowed variant Holds &'a B - no allocation
Owned variant Holds <B as ToOwned>::Owned
to_mut() Returns &mut to owned data, clones if borrowed
into_owned() Extracts owned data, clones if borrowed

When Cow helps:

  • Static defaults with dynamic overrides
  • Conditional transformations
  • Error messages mixing static and dynamic content
  • Path and string normalization
  • API flexibility for callers

When to avoid Cow:

  • Always modifying data
  • Complex lifetime interactions
  • Micro-optimization that complicates code
  • Ownership must be guaranteed

Key insight: Cow is most valuable when the borrowed case is common and the owned case is rare, letting you avoid allocations for the majority of inputs while still handling modifications when necessary.