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

Walkthrough

Cow<'a, B> (Clone on Write) is a smart pointer type that can hold either borrowed data (&B) or owned data (B). It allows you to defer cloning until modification is needed, optimizing for cases where data is often used read-only.

Key concepts:

  • Borrowed or Owned — Either &'a B (borrowed) or B (owned)
  • Lazy cloning — Only clones when mutation is needed
  • ToOwned trait — Types that can be converted from borrowed to owned
  • Borrow trait — Types that can be borrowed from owned

Common use cases:

  • Functions that may or may not need to modify input
  • Returning either static or dynamic strings
  • Avoiding unnecessary allocations
  • Processing pipelines with optional transformations

When Cow shines:

  • Most operations are read-only
  • Cloning is expensive
  • You want to avoid unnecessary allocations
  • Returning either borrowed or owned data from a function

Code Examples

Basic Cow Usage

use std::borrow::Cow;
 
fn main() {
    // Cow can hold borrowed data
    let borrowed: Cow<str> = Cow::Borrowed("hello");
    println!("Borrowed: {}", borrowed);
    
    // Cow can hold owned data
    let owned: Cow<str> = Cow::Owned(String::from("hello world"));
    println!("Owned: {}", owned);
    
    // Both can be used the same way
    fn print_cow(s: Cow<str>) {
        println!("Value: {}", s);
    }
    
    print_cow(borrowed);
    print_cow(owned);
}

Conditional Modification

use std::borrow::Cow;
 
fn add_prefix<'a>(input: Cow<'a, str>) -> Cow<'a, str> {
    if input.starts_with("prefix_") {
        // No modification needed, return as-is
        input
    } else {
        // Need to modify, so we clone and modify
        let mut modified = input.into_owned();
        modified.insert_str(0, "prefix_");
        Cow::Owned(modified)
    }
}
 
fn main() {
    // Already has prefix - no allocation
    let s1 = Cow::Borrowed("prefix_hello");
    let result1 = add_prefix(s1);
    println!("Result 1: {}", result1);
    
    // Needs prefix - allocates
    let s2 = Cow::Borrowed("world");
    let result2 = add_prefix(s2);
    println!("Result 2: {}", result2);
}

Avoiding Unnecessary Clones

use std::borrow::Cow;
 
fn process_string<'a>(input: &'a str) -> Cow<'a, str> {
    // If the string is already lowercase, return borrowed
    // Otherwise, create an owned lowercase version
    if input.chars().all(|c| !c.is_ascii_uppercase()) {
        Cow::Borrowed(input)
    } else {
        Cow::Owned(input.to_lowercase())
    }
}
 
fn main() {
    // No allocation needed
    let result1 = process_string("hello world");
    println!("Result 1: {}", result1);
    
    // Allocation needed
    let result2 = process_string("Hello World");
    println!("Result 2: {}", result2);
}

Cow with Function Parameters

use std::borrow::Cow;
 
// Accept both &str and String without forcing ownership
fn greet<'a>(name: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
    let name = name.into();
    
    if name.is_empty() {
        Cow::Borrowed("Anonymous")
    } else {
        name
    }
}
 
fn main() {
    // Pass &str
    let result1 = greet("Alice");
    println!("{}", result1);
    
    // Pass String
    let result2 = greet(String::from("Bob"));
    println!("{}", result2);
    
    // Pass empty
    let result3 = greet("");
    println!("{}", result3);
}

Cow with PathBuf

use std::borrow::Cow;
use std::path::{Path, PathBuf};
 
fn canonicalize_path<'a>(path: &'a Path) -> Cow<'a, Path> {
    // If path is already absolute, return as-is
    if path.is_absolute() {
        Cow::Borrowed(path)
    } else {
        // Otherwise, create owned absolute path
        let mut absolute = std::env::current_dir().unwrap();
        absolute.push(path);
        Cow::Owned(absolute)
    }
}
 
fn main() {
    // Relative path - needs allocation
    let relative = std::path::Path::new("src/main.rs");
    let result1 = canonicalize_path(relative);
    println!("Result 1: {:?}", result1);
    
    // Absolute path - no allocation
    let absolute = std::path::Path::new("/usr/bin/rustc");
    let result2 = canonicalize_path(absolute);
    println!("Result 2: {:?}", result2);
}

Cow with Vec (slices)

use std::borrow::Cow;
 
fn add_element<'a>(input: Cow<'a, [i32]>, element: i32) -> Cow<'a, [i32]> {
    // Check if element already exists
    if input.contains(&element) {
        return input;
    }
    
    // Need to add element - clone and modify
    let mut result = input.into_owned();
    result.push(element);
    Cow::Owned(result)
}
 
fn main() {
    let data = vec![1, 2, 3, 4, 5];
    
    // Element exists - no allocation
    let result1 = add_element(Cow::Borrowed(&data), 3);
    println!("Result 1: {:?}", result1);
    
    // Element doesn't exist - allocates
    let result2 = add_element(Cow::Borrowed(&data), 6);
    println!("Result 2: {:?}", result2);
}

to_mut() for Lazy Cloning

use std::borrow::Cow;
 
fn main() {
    let borrowed: Cow<str> = Cow::Borrowed("hello");
    
    // to_mut() returns &mut only if we need to modify
    // It clones on first mutation
    let mut cow = Cow::Borrowed("hello");
    
    // First call to to_mut() - no clone needed yet
    // Just reading
    if cow.starts_with("he") {
        println!("Starts with 'he'");
    }
    
    // Need to modify - to_mut() will clone here
    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),
    }
}

into_owned() for Ownership

use std::borrow::Cow;
 
fn main() {
    let borrowed: Cow<str> = Cow::Borrowed("hello");
    
    // into_owned() always produces owned data
    // Clones if currently borrowed
    let owned: String = borrowed.into_owned();
    println!("Owned: {}", owned);
    
    // If already owned, just returns the inner value
    let already_owned: Cow<str> = Cow::Owned(String::from("world"));
    let result: String = already_owned.into_owned();
    println!("Result: {}", result);
}

Processing Pipeline

use std::borrow::Cow;
 
fn trim<'a>(input: Cow<'a, str>) -> Cow<'a, str> {
    let trimmed = input.trim();
    if trimmed.len() == input.len() {
        input
    } else {
        Cow::Owned(trimmed.to_string())
    }
}
 
fn uppercase<'a>(input: Cow<'a, str>) -> Cow<'a, str> {
    if input.chars().all(|c| !c.is_ascii_lowercase()) {
        input
    } else {
        Cow::Owned(input.to_uppercase())
    }
}
 
fn add_suffix<'a>(input: Cow<'a, str>, suffix: &str) -> Cow<'a, str> {
    if input.ends_with(suffix) {
        input
    } else {
        let mut result = input.into_owned();
        result.push_str(suffix);
        Cow::Owned(result)
    }
}
 
fn process<'a>(input: &'a str) -> Cow<'a, str> {
    let result = Cow::Borrowed(input);
    let result = trim(result);
    let result = uppercase(result);
    let result = add_suffix(result, "!");
    result
}
 
fn main() {
    // No modifications needed
    let result1 = process("HELLO!");
    println!("Result 1: {}", result1);
    
    // Needs modifications
    let result2 = process("  hello  ");
    println!("Result 2: {}", result2);
}

Returning Static or Dynamic Strings

use std::borrow::Cow;
 
fn get_message(code: u32) -> Cow<'static, str> {
    match code {
        0 => Cow::Borrowed("Success"),
        1 => Cow::Borrowed("Invalid input"),
        2 => Cow::Borrowed("Not found"),
        // Dynamic error message
        n => Cow::Owned(format!("Unknown error code: {}", n)),
    }
}
 
fn main() {
    println!("Error 0: {}", get_message(0));
    println!("Error 1: {}", get_message(1));
    println!("Error 42: {}", get_message(42));
}

Cow with Custom Types

use std::borrow::Cow;
 
#[derive(Debug, Clone)]
struct Config {
    name: String,
    value: i32,
}
 
impl std::borrow::Borrow<str> for Config {
    fn borrow(&self) -> &str {
        &self.name
    }
}
 
impl ToOwned for Config {
    type Owned = Config;
    
    fn to_owned(&self) -> Config {
        self.clone()
    }
}
 
fn get_config<'a>(name: &str, configs: &'a [Config]) -> Cow<'a, Config> {
    // Find config by name
    if let Some(config) = configs.iter().find(|c| c.name == name) {
        return Cow::Borrowed(config);
    }
    
    // Return default config (owned)
    Cow::Owned(Config {
        name: name.to_string(),
        value: 0,
    })
}
 
fn main() {
    let configs = vec![
        Config { name: "timeout".to_string(), value: 30 },
        Config { name: "retries".to_string(), value: 3 },
    ];
    
    // Found - returns borrowed
    let result1 = get_config("timeout", &configs);
    println!("Found: {:?}", result1);
    
    // Not found - returns owned default
    let result2 = get_config("max_size", &configs);
    println!("Default: {:?}", result2);
}

Cow for Configuration Defaults

use std::borrow::Cow;
 
struct Settings {
    defaults: Vec<(String, String)>,
}
 
impl Settings {
    fn new() -> Self {
        Self {
            defaults: vec![
                ("host".to_string(), "localhost".to_string()),
                ("port".to_string(), "8080".to_string()),
                ("timeout".to_string(), "30".to_string()),
            ],
        }
    }
    
    fn get<'a>(&'a self, key: &str, user_value: Option<&'a str>) -> Cow<'a, str> {
        match user_value {
            Some(value) => Cow::Borrowed(value),
            None => {
                // Look up default
                self.defaults
                    .iter()
                    .find(|(k, _)| k == key)
                    .map(|(_, v)| Cow::Borrowed(v.as_str()))
                    .unwrap_or_else(|| Cow::Owned(String::new()))
            }
        }
    }
}
 
fn main() {
    let settings = Settings::new();
    
    // User-provided value
    let result1 = settings.get("host", Some("example.com"));
    println!("Host: {}", result1);
    
    // Default value
    let result2 = settings.get("port", None);
    println!("Port: {}", result2);
}

Cow in Structs

use std::borrow::Cow;
 
struct Message<'a> {
    title: Cow<'a, str>,
    body: Cow<'a, str>,
}
 
impl<'a> Message<'a> {
    fn new(title: impl Into<Cow<'a, str>>, body: impl Into<Cow<'a, str>>) -> Self {
        Self {
            title: title.into(),
            body: body.into(),
        }
    }
    
    fn with_static_title(title: &'static str) -> Self {
        Self {
            title: Cow::Borrowed(title),
            body: Cow::Owned(String::new()),
        }
    }
}
 
fn main() {
    // Static title, dynamic body
    let msg1 = Message::new("Alert", String::from("Something happened!"));
    println!("Title: {}, Body: {}", msg1.title, msg1.body);
    
    // Both borrowed
    let msg2 = Message::new("Warning", "Check this out");
    println!("Title: {}, Body: {}", msg2.title, msg2.body);
}

Cow with Bytes

use std::borrow::Cow;
 
fn process_bytes<'a>(input: &'a [u8]) -> Cow<'a, [u8]> {
    // Check if all bytes are ASCII
    if input.iter().all(|b| b.is_ascii()) {
        // Check if all lowercase
        if input.iter().all(|b| !b.is_ascii_uppercase()) {
            return Cow::Borrowed(input);
        }
    }
    
    // Need to process - create owned
    let processed: Vec<u8> = input
        .iter()
        .map(|b| b.to_ascii_lowercase())
        .collect();
    
    Cow::Owned(processed)
}
 
fn main() {
    // Already lowercase ASCII - no allocation
    let data1 = b"hello world";
    let result1 = process_bytes(data1);
    println!("Result 1: {:?}", result1);
    
    // Needs processing - allocates
    let data2 = b"Hello World";
    let result2 = process_bytes(data2);
    println!("Result 2: {:?}", result2);
}

Pattern Matching on Cow

use std::borrow::Cow;
 
fn describe_cow(cow: &Cow<str>) -> &'static str {
    match cow {
        Cow::Borrowed(s) => {
            println!("Borrowed: {}", s);
            "borrowed"
        }
        Cow::Owned(s) => {
            println!("Owned: {}", s);
            "owned"
        }
    }
}
 
fn main() {
    let borrowed: Cow<str> = Cow::Borrowed("hello");
    let owned: Cow<str> = Cow::Owned(String::from("world"));
    
    println!("First is: {}", describe_cow(&borrowed));
    println!("Second is: {}", describe_cow(&owned));
}

Summary

Cow<'a, B> Variants:

Variant Holds When to Use
Borrowed(&'a B) Reference Data already exists, no modification needed
Owned(B) Owned value Data was modified or created

Key Methods:

Method Description
Borrowed(val) Create borrowed Cow
Owned(val) Create owned Cow
into_owned() Get owned value (clones if needed)
to_mut() Get mutable reference (clones if borrowed)
is_borrowed() Check if borrowed
is_owned() Check if owned

Common Type Parameters:

Cow Type Borrowed Owned
Cow<'a, str> &'a str String
Cow<'a, [T]> &'a [T] Vec<T>
Cow<'a, Path> &'a Path PathBuf
Cow<'a, CStr> &'a CStr CString
Cow<'a, OsStr> &'a OsStr OsString

When to Use Cow:

Scenario Appropriate?
Read-mostly data āœ… Yes
Avoiding unnecessary clones āœ… Yes
Returning static or dynamic strings āœ… Yes
Processing pipelines āœ… Yes
Always modifying data āŒ Just use owned
Short-lived operations āŒ Just clone

Key Points:

  • Cow holds either borrowed (&T) or owned (T) data
  • to_mut() clones only when first mutation is needed
  • into_owned() returns owned, cloning if necessary
  • Works with any type implementing ToOwned trait
  • Common with str, [T], Path, OsStr, CStr
  • Great for optimizing read-heavy operations with occasional writes
  • Lifetime parameter 'a allows returning borrowed data from function
  • Cow<'static, str> can hold static strings without allocation