Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cow (Clone on Write) Pattern

Clone on Write (Cow) is a optimization pattern that defers cloning expensive data until mutation is actually needed. Rust's Cow<'a, T> type provides this functionality, allowing functions to accept both owned and borrowed data while only cloning when necessary.

Problem Statement

You want to write functions that can accept both owned and borrowed data, and you want to avoid unnecessary cloning. Sometimes you need to return data that might be a reference to input data (zero-copy) or newly allocated data (when modification is needed).

Understanding Clone on Write

The core idea is simple: keep a reference to data as long as possible, only clone when you need to modify it.

#![allow(unused)]
fn main() {
use std::borrow::Cow;

// Without Cow - always clones
fn process_string_clone(input: &str) -> String {
    let mut result = input.to_string();  // Always clones
    if input.contains("ERROR") {
        result = result.replace("ERROR", "WARNING");
    }
    result
}

// With Cow - clones only when needed
fn process_string_cow(input: &str) -> Cow<str> {
    if input.contains("ERROR") {
        Cow::Owned(input.replace("ERROR", "WARNING"))  // Clone only when modifying
    } else {
        Cow::Borrowed(input)  // No cloning needed
    }
}
}

Cow<'a, T> Enum Structure

#![allow(unused)]
fn main() {
pub enum Cow<'a, T: ?Sized + ToOwned> {
    Borrowed(&'a T),
    Owned(<T as ToOwned>::Owned),
}
}

The Cow enum has two variants:

  • Borrowed(&'a T) - holds a reference to existing data
  • Owned(T::Owned) - holds owned data

Basic Cow Usage

String Processing Examples

#![allow(unused)]
fn main() {
use std::borrow::Cow;

fn remove_whitespace(input: &str) -> Cow<str> {
    if input.contains(char::is_whitespace) {
        // Need to modify - create owned string
        Cow::Owned(input.chars().filter(|c| !c.is_whitespace()).collect())
    } else {
        // No modification needed - return borrowed
        Cow::Borrowed(input)
    }
}

fn add_prefix(input: &str, prefix: &str) -> Cow<str> {
    if input.starts_with(prefix) {
        Cow::Borrowed(input)  // Already has prefix
    } else {
        Cow::Owned(format!("{}{}", prefix, input))  // Need to add prefix
    }
}

fn normalize_case(input: &str) -> Cow<str> {
    if input.chars().all(|c| c.is_lowercase()) {
        Cow::Borrowed(input)  // Already lowercase
    } else {
        Cow::Owned(input.to_lowercase())  // Convert to lowercase
    }
}

fn demonstrate_string_processing() {
    println!("=== String Processing with Cow ===");

    let text1 = "hello world";
    let text2 = "hello   world   with   spaces";

    println!("Original: '{}'", text1);
    match remove_whitespace(text1) {
        Cow::Borrowed(s) => println!("No whitespace (borrowed): '{}'", s),
        Cow::Owned(s) => println!("Whitespace removed (owned): '{}'", s),
    }

    println!("Original: '{}'", text2);
    match remove_whitespace(text2) {
        Cow::Borrowed(s) => println!("No whitespace (borrowed): '{}'", s),
        Cow::Owned(s) => println!("Whitespace removed (owned): '{}'", s),
    }

    // Prefix examples
    let url1 = "https://example.com";
    let url2 = "example.com";

    println!("URL1: {} -> {}", url1, add_prefix(url1, "https://"));
    println!("URL2: {} -> {}", url2, add_prefix(url2, "https://"));
}
}

Path Processing

#![allow(unused)]
fn main() {
use std::borrow::Cow;
use std::path::Path;

fn normalize_path(path: &Path) -> Cow<Path> {
    // Check if path needs normalization
    let path_str = path.to_string_lossy();

    if path_str.contains("//") || path_str.contains("/./") || path_str.contains("/../") {
        // Need to normalize - create owned PathBuf
        let mut components = Vec::new();
        for component in path.components() {
            match component {
                std::path::Component::CurDir => {
                    // Skip current directory "."
                }
                std::path::Component::ParentDir => {
                    // Handle parent directory ".."
                    components.pop();
                }
                other => {
                    components.push(other);
                }
            }
        }

        let mut normalized = std::path::PathBuf::new();
        for component in components {
            normalized.push(component);
        }

        Cow::Owned(normalized)
    } else {
        // Path is already normalized
        Cow::Borrowed(path)
    }
}

fn ensure_extension(path: &Path, ext: &str) -> Cow<Path> {
    if path.extension().and_then(|e| e.to_str()) == Some(ext) {
        Cow::Borrowed(path)
    } else {
        let mut owned = path.to_path_buf();
        owned.set_extension(ext);
        Cow::Owned(owned)
    }
}

fn demonstrate_path_processing() {
    println!("\n=== Path Processing with Cow ===");

    let paths = [
        Path::new("/home/user/documents"),
        Path::new("/home/user//documents/./file.txt"),
        Path::new("/home/user/documents/../downloads/file.txt"),
    ];

    for path in &paths {
        println!("Original: {:?}", path);
        match normalize_path(path) {
            Cow::Borrowed(p) => println!("  Already normalized (borrowed): {:?}", p),
            Cow::Owned(p) => println!("  Normalized (owned): {:?}", p),
        }
    }

    // Extension examples
    let file1 = Path::new("document.txt");
    let file2 = Path::new("document");

    println!("File1: {:?} -> {:?}", file1, ensure_extension(file1, "txt"));
    println!("File2: {:?} -> {:?}", file2, ensure_extension(file2, "txt"));
}
}

Advanced Cow Patterns

Configuration Processing

#![allow(unused)]
fn main() {
use std::borrow::Cow;
use std::collections::HashMap;

#[derive(Debug, Clone)]
struct Config {
    settings: HashMap<String, String>,
}

impl Config {
    fn new() -> Self {
        Config {
            settings: HashMap::new(),
        }
    }

    fn get_or_default<'a>(&'a self, key: &str, default: &'a str) -> Cow<'a, str> {
        self.settings.get(key)
            .map(|s| Cow::Borrowed(s.as_str()))
            .unwrap_or(Cow::Borrowed(default))
    }

    fn get_processed<'a>(&'a self, key: &str) -> Option<Cow<'a, str>> {
        self.settings.get(key).map(|value| {
            if value.starts_with('$') {
                // Environment variable substitution
                let var_name = &value[1..];
                match std::env::var(var_name) {
                    Ok(env_value) => Cow::Owned(env_value),
                    Err(_) => Cow::Borrowed(value.as_str()),
                }
            } else {
                Cow::Borrowed(value.as_str())
            }
        })
    }
}

fn demonstrate_config() {
    println!("\n=== Configuration Processing ===");

    let mut config = Config::new();
    config.settings.insert("host".to_string(), "localhost".to_string());
    config.settings.insert("port".to_string(), "8080".to_string());
    config.settings.insert("env_path".to_string(), "$PATH".to_string());

    // Get with defaults
    println!("Host: {}", config.get_or_default("host", "0.0.0.0"));
    println!("Timeout: {}", config.get_or_default("timeout", "30"));

    // Process environment variables
    if let Some(path) = config.get_processed("env_path") {
        match path {
            Cow::Borrowed(s) => println!("Path (borrowed): {}", s),
            Cow::Owned(s) => println!("Path (from env): {}", &s[..50.min(s.len())]),
        }
    }
}
}

Data Transformation Pipeline

#![allow(unused)]
fn main() {
use std::borrow::Cow;

trait Transform {
    fn transform<'a>(&self, input: Cow<'a, str>) -> Cow<'a, str>;
}

struct TrimTransform;
impl Transform for TrimTransform {
    fn transform<'a>(&self, input: Cow<'a, str>) -> Cow<'a, str> {
        let trimmed = input.trim();
        if trimmed.len() == input.len() {
            input  // No change needed
        } else {
            Cow::Owned(trimmed.to_string())
        }
    }
}

struct UppercaseTransform;
impl Transform for UppercaseTransform {
    fn transform<'a>(&self, input: Cow<'a, str>) -> Cow<'a, str> {
        if input.chars().all(|c| c.is_uppercase() || !c.is_alphabetic()) {
            input  // Already uppercase
        } else {
            Cow::Owned(input.to_uppercase())
        }
    }
}

struct ReplaceTransform {
    from: String,
    to: String,
}

impl ReplaceTransform {
    fn new(from: &str, to: &str) -> Self {
        ReplaceTransform {
            from: from.to_string(),
            to: to.to_string(),
        }
    }
}

impl Transform for ReplaceTransform {
    fn transform<'a>(&self, input: Cow<'a, str>) -> Cow<'a, str> {
        if input.contains(&self.from) {
            Cow::Owned(input.replace(&self.from, &self.to))
        } else {
            input
        }
    }
}

struct Pipeline {
    transforms: Vec<Box<dyn Transform>>,
}

impl Pipeline {
    fn new() -> Self {
        Pipeline {
            transforms: Vec::new(),
        }
    }

    fn add_transform(mut self, transform: Box<dyn Transform>) -> Self {
        self.transforms.push(transform);
        self
    }

    fn process<'a>(&self, input: &'a str) -> Cow<'a, str> {
        let mut current = Cow::Borrowed(input);
        for transform in &self.transforms {
            current = transform.transform(current);
        }
        current
    }
}

fn demonstrate_pipeline() {
    println!("\n=== Transformation Pipeline ===");

    let pipeline = Pipeline::new()
        .add_transform(Box::new(TrimTransform))
        .add_transform(Box::new(ReplaceTransform::new("error", "warning")))
        .add_transform(Box::new(UppercaseTransform));

    let inputs = [
        "  hello world  ",
        "ALREADY UPPERCASE",
        "  this has an error message  ",
        "clean text",
    ];

    for input in &inputs {
        println!("Input: '{}'", input);
        match pipeline.process(input) {
            Cow::Borrowed(s) => println!("  Output (borrowed): '{}'", s),
            Cow::Owned(s) => println!("  Output (owned): '{}'", s),
        }
    }
}
}

Custom ToOwned Implementation

#![allow(unused)]
fn main() {
use std::borrow::{Cow, ToOwned};

// Custom type that can be borrowed/owned
#[derive(Debug, Clone, PartialEq)]
struct CustomData {
    values: Vec<i32>,
}

// Borrowed version
#[derive(Debug, PartialEq)]
struct CustomDataRef<'a> {
    values: &'a [i32],
}

impl ToOwned for CustomDataRef<'_> {
    type Owned = CustomData;

    fn to_owned(&self) -> Self::Owned {
        CustomData {
            values: self.values.to_vec(),
        }
    }
}

impl<'a> From<&'a CustomData> for CustomDataRef<'a> {
    fn from(data: &'a CustomData) -> Self {
        CustomDataRef {
            values: &data.values,
        }
    }
}

fn process_custom_data(input: CustomDataRef) -> Cow<CustomDataRef> {
    // Only clone if we need to modify
    if input.values.iter().any(|&x| x < 0) {
        // Need to filter out negative numbers
        let filtered: Vec<i32> = input.values.iter().filter(|&&x| x >= 0).copied().collect();
        let owned = CustomData { values: filtered };
        Cow::Owned(owned)
    } else {
        Cow::Borrowed(input)
    }
}

fn demonstrate_custom_toowned() {
    println!("\n=== Custom ToOwned Implementation ===");

    let data1 = CustomData { values: vec![1, 2, 3, 4, 5] };
    let data2 = CustomData { values: vec![1, -2, 3, -4, 5] };

    let ref1 = CustomDataRef::from(&data1);
    let ref2 = CustomDataRef::from(&data2);

    match process_custom_data(ref1) {
        Cow::Borrowed(r) => println!("No negatives (borrowed): {:?}", r),
        Cow::Owned(o) => println!("Filtered (owned): {:?}", o),
    }

    match process_custom_data(ref2) {
        Cow::Borrowed(r) => println!("No negatives (borrowed): {:?}", r),
        Cow::Owned(o) => println!("Filtered (owned): {:?}", o),
    }
}
}

Complete Example and Usage

fn main() {
    println!("=== Cow (Clone on Write) Pattern Examples ===");

    // Basic string processing
    demonstrate_string_processing();

    // Path processing
    demonstrate_path_processing();

    // Configuration processing
    demonstrate_config();

    // Transformation pipeline
    demonstrate_pipeline();

    // Custom ToOwned
    demonstrate_custom_toowned();

    // Performance comparison
    demonstrate_performance();
}

fn demonstrate_performance() {
    println!("\n=== Performance Comparison ===");

    let large_string = "a".repeat(1_000_000);
    let iterations = 1000;

    // Measure always-cloning approach
    let start = std::time::Instant::now();
    for _ in 0..iterations {
        let _result = always_clone(&large_string);
    }
    let always_clone_time = start.elapsed();

    // Measure cow approach (no modification needed)
    let start = std::time::Instant::now();
    for _ in 0..iterations {
        let _result = cow_approach(&large_string);
    }
    let cow_time = start.elapsed();

    println!("Always clone: {:?}", always_clone_time);
    println!("Cow (no modification): {:?}", cow_time);
    println!("Speedup: {:.2}x", always_clone_time.as_secs_f64() / cow_time.as_secs_f64());
}

fn always_clone(input: &str) -> String {
    let result = input.to_string();  // Always clone
    if result.contains("xyz") {
        result.replace("xyz", "abc")
    } else {
        result
    }
}

fn cow_approach(input: &str) -> Cow<str> {
    if input.contains("xyz") {
        Cow::Owned(input.replace("xyz", "abc"))
    } else {
        Cow::Borrowed(input)  // No clone needed
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cow_borrowed_case() {
        let input = "hello world";
        let result = remove_whitespace(input);

        match result {
            Cow::Borrowed(s) => assert_eq!(s, "hello world"),
            Cow::Owned(_) => panic!("Expected borrowed, got owned"),
        }
    }

    #[test]
    fn test_cow_owned_case() {
        let input = "hello   world";
        let result = remove_whitespace(input);

        match result {
            Cow::Borrowed(_) => panic!("Expected owned, got borrowed"),
            Cow::Owned(s) => assert_eq!(s, "helloworld"),
        }
    }

    #[test]
    fn test_prefix_addition() {
        assert_eq!(add_prefix("https://example.com", "https://"), "https://example.com");
        assert_eq!(add_prefix("example.com", "https://"), "https://example.com");
    }

    #[test]
    fn test_transformation_pipeline() {
        let pipeline = Pipeline::new()
            .add_transform(Box::new(TrimTransform))
            .add_transform(Box::new(UppercaseTransform));

        let result = pipeline.process("  hello  ");
        assert_eq!(result, "HELLO");
    }
}

When to Use Cow

Use Cow when:

  • You frequently don't need to modify input data
  • You want to avoid unnecessary allocations
  • You're building APIs that should accept both owned and borrowed data
  • You're implementing transformation pipelines
  • You need to conditionally modify data

Don't use Cow when:

  • You always need to modify the data
  • The data is small and cloning is cheap
  • The complexity isn't worth the optimization
  • You're working with non-cloneable data

Performance Characteristics

  • Best case: Zero allocations when no modification is needed
  • Worst case: Same cost as always cloning
  • Memory: Only uses extra memory when cloning is necessary
  • CPU: Small overhead for the enum check, big savings when avoiding clones

API Design with Cow

#![allow(unused)]
fn main() {
// Good: Flexible API that accepts both owned and borrowed
fn process_text(input: impl Into<Cow<str>>) -> Cow<str> {
    let text = input.into();
    // Process the text...
    text
}

// Usage:
process_text("borrowed string");           // &str
process_text(String::from("owned"));       // String
process_text(Cow::Borrowed("explicit"));   // Explicit Cow
}

Core Rust concepts introduced

Building on previous patterns, the Cow pattern introduces several new concepts related to memory efficiency and flexible APIs:

1. Cow<'a, T> Enum and Borrowed/Owned Variants

#![allow(unused)]
fn main() {
pub enum Cow<'a, T: ?Sized + ToOwned> {
    Borrowed(&'a T),                    // Reference to existing data
    Owned(<T as ToOwned>::Owned),       // Owned copy of the data
}
}
  • Delayed cloning: Only clone when mutation is actually needed
  • Lifetime parameter: 'a tracks how long borrowed data is valid
  • Type flexibility: Can hold either borrowed or owned data of the same logical type
  • Pattern matching: Use match to handle both cases explicitly

2. Lazy Cloning and Performance Optimization

#![allow(unused)]
fn main() {
fn process_data(input: &str) -> Cow<str> {
    if needs_modification(input) {
        Cow::Owned(expensive_transformation(input))  // Clone only when needed
    } else {
        Cow::Borrowed(input)  // Zero-cost when no modification needed
    }
}
}
  • Conditional cloning: Only allocate memory when modification is required
  • Best-case zero-cost: No allocation when data doesn't need changes
  • Worst-case equivalent: Same cost as always cloning when modification is needed
  • Memory efficiency: Significant savings in read-heavy workloads

3. Lifetime Elision in Return Types

#![allow(unused)]
fn main() {
// Explicit lifetimes
fn process<'a>(input: &'a str) -> Cow<'a, str> { ... }

// Lifetime elision (compiler infers)
fn process(input: &str) -> Cow<str> { ... }
}
  • Lifetime elision rules: Compiler can infer lifetimes in many cases
  • Input/output relationship: Lifetime of output tied to lifetime of input
  • API simplification: Less verbose function signatures
  • Borrow checker integration: Ensures borrowed data lives long enough

4. ToOwned Trait and Custom Implementations

#![allow(unused)]
fn main() {
pub trait ToOwned {
    type Owned: Borrow<Self>;

    fn to_owned(&self) -> Self::Owned;

    fn clone_into(&self, target: &mut Self::Owned) { ... }
}
}
  • Generalized cloning: More flexible than Clone for borrowed/owned relationships
  • Associated type: Owned represents the owned version of borrowed data
  • Standard implementations: str -> String, [T] -> Vec<T>, Path -> PathBuf
  • Custom implementations: Define your own borrowed/owned type pairs

5. Zero-Copy String Processing

#![allow(unused)]
fn main() {
fn remove_prefix(input: &str, prefix: &str) -> Cow<str> {
    if let Some(stripped) = input.strip_prefix(prefix) {
        Cow::Borrowed(stripped)  // Zero-copy slice of original string
    } else {
        Cow::Borrowed(input)     // No modification needed
    }
}
}
  • String slicing: Create views into existing strings without allocation
  • Conditional processing: Only allocate when modification is actually needed
  • Memory savings: Dramatic reduction in allocations for read-heavy operations
  • Cache-friendly: Better memory locality when avoiding allocations

6. API Design for Flexible Input Types

#![allow(unused)]
fn main() {
// Accept multiple input types
fn flexible_api(input: impl Into<Cow<str>>) -> String {
    let cow = input.into();
    cow.into_owned()  // Convert to owned String
}

// Usage examples:
flexible_api("string literal");        // &str -> Cow::Borrowed
flexible_api(String::from("owned"));   // String -> Cow::Owned
flexible_api(Cow::Borrowed("cow"));    // Direct Cow
}
  • Into<Cow<T>> pattern: Accept both borrowed and owned data seamlessly
  • Conversion traits: Automatic conversion from &T and T::Owned to Cow<T>
  • API flexibility: Single function signature handles multiple input types
  • User convenience: Callers don't need to think about ownership

7. Memory Efficiency Patterns

#![allow(unused)]
fn main() {
// Efficient transformation pipeline
fn transform_pipeline(input: &str) -> Cow<str> {
    let mut current = Cow::Borrowed(input);

    for transform in &transforms {
        current = transform.apply(current);  // Chain without unnecessary cloning
    }

    current
}
}
  • Pipeline efficiency: Avoid intermediate clones in transformation chains
  • Lazy evaluation: Defer expensive operations until absolutely necessary
  • Memory pressure reduction: Significant savings in allocation-heavy code
  • Performance monitoring: Easy to track when allocations actually occur

These concepts demonstrate how Rust enables both memory efficiency and API flexibility through the type system. Cow is a powerful pattern for building performant code that doesn't sacrifice usability, allowing you to optimize for the common case (no modification needed) while still handling the general case (modification required) correctly.