What is the Cow str type

Cow<'a, str> is a Rust enum that efficiently handles both borrowed string slices and owned strings, cloning only when modification is required.

When borrowing isn't enough, but owning is too much

You're building a text processing pipeline. It reads lines from a file, applies a series of transformations, and writes the result. Most lines are short and come from a buffer you control. Some lines are huge and live in a shared cache. You want your transformation function to accept both without forcing a copy every time.

If you take &str, you can't modify the string in place. You'd have to allocate a new String for every line, even the ones that don't change. If you take String, you force the caller to allocate and copy, even when the data is already owned by the caller and just needs a quick tweak. You need a type that says "I can be borrowed or owned, and I'll handle the switch if I have to."

That type is Cow<str>. The name stands for "Clone on Write." It bridges the gap between zero-cost borrowing and safe mutation. It pays for itself the moment you skip an allocation.

Clone on Write explained

Cow is an enum from the standard library. It holds either a borrowed reference or an owned value. The "Clone on Write" name describes the behavior: you start with a reference. As long as you only read, you keep the reference. The moment you try to write, Cow clones the data into an owned buffer so you can mutate it safely.

Think of a library book. You check it out and read it. That's borrowed. You can't write notes in the margins. If you need to annotate it, you buy a personal copy. Now you own it, and you can scribble all you want. Cow automates that transition. It tracks whether the data is borrowed or owned, and it performs the clone only when mutation is required.

For strings, the type is Cow<'a, str>. The lifetime 'a applies to the borrowed variant. The owned variant is a String. You can treat both variants as a string slice, but only the owned variant allows mutation.

Minimal example

Here is the basic pattern. You create a Cow, check if you need to modify it, and use to_mut() to trigger the clone if necessary.

use std::borrow::Cow;

/// Converts a string to uppercase.
/// Accepts borrowed or owned data.
fn to_upper(text: Cow<str>) -> String {
    // Clone on write: to_mut() checks the variant.
    // If Borrowed, it allocates and copies.
    // If Owned, it returns the mutable reference immediately.
    let mut working = text;
    working.to_mut().make_ascii_uppercase();
    
    // Extract the owned string.
    // If already owned, this is free.
    // If borrowed, this clones.
    working.into_owned()
}

fn main() {
    // Borrowed: no allocation. Points to static data.
    let borrowed = Cow::Borrowed("hello");
    println!("{}", to_upper(borrowed));

    // Owned: already a String. No extra copy needed.
    let owned = Cow::Owned(String::from("world"));
    println!("{}", to_upper(owned));
}

The key method is to_mut(). It returns a &mut str. If the Cow is Borrowed, to_mut() allocates a new String, copies the data, switches the enum variant to Owned, and returns the mutable reference. If the Cow is already Owned, to_mut() just returns the mutable reference. No allocation. No copy.

Trust the check. The branch predictor handles the variant check efficiently, and the allocation savings are real when mutation is rare.

What happens under the hood

Cow<'a, B> is defined roughly like this:

enum Cow<'a, B>
where
    B: ToOwned + ?Sized,
{
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}

For Cow<str>, B is str. The ToOwned trait for str has an associated type Owned which is String. So Cow<'a, str> is effectively Borrowed(&'a str) or Owned(String).

When you call to_mut(), the implementation matches on the enum. If it's Borrowed, it calls ToOwned::to_owned() to create the owned variant. For strings, this allocates. If it's Owned, it returns &mut self. The cost of the match is negligible compared to the allocation you avoid.

This pattern shines when mutation is rare. If you mutate every single time, Cow adds overhead for nothing. The enum indirection and the check cost more than just using a String. If you never mutate, Cow costs almost nothing. It's just a wrapper around a reference.

Realistic usage: sanitizing input

A common use case is a function that validates or sanitizes input. Most inputs are clean. Some need fixing. You can return Cow to avoid allocation for the clean cases.

use std::borrow::Cow;

/// Sanitizes a string by replacing tabs with spaces.
/// Returns Borrowed if no tabs are found.
/// Returns Owned if tabs were replaced.
fn sanitize(input: &str) -> Cow<str> {
    // Check if the string contains tabs.
    // If not, return the borrowed slice. No allocation.
    if input.contains('\t') {
        // Tabs found. We must allocate and replace.
        let mut result = String::with_capacity(input.len());
        for c in input.chars() {
            if c == '\t' {
                result.push_str("    ");
            } else {
                result.push(c);
            }
        }
        Cow::Owned(result)
    } else {
        // Clean string. Return borrowed reference.
        Cow::Borrowed(input)
    }
}

fn main() {
    let clean = "no tabs here";
    let dirty = "has\ta\ttab";

    // Returns Borrowed. Zero cost.
    let result_clean = sanitize(clean);
    assert!(matches!(result_clean, Cow::Borrowed(_)));

    // Returns Owned. Allocation happened.
    let result_dirty = sanitize(dirty);
    assert!(matches!(result_dirty, Cow::Owned(_)));
}

Returning Cow signals to the caller that you respected their memory. The caller can check the variant if they care, or just use the result as a string. This is a polite way to say "I won't copy your data unless I have to."

The transparency trick: Deref and AsRef

Cow works seamlessly with existing APIs because it implements Deref and AsRef. This is a convention that makes Cow feel like a first-class string type.

Cow<str> implements Deref<Target=str>. This means you can pass &cow to any function expecting &str. The compiler inserts the dereference automatically.

use std::borrow::Cow;

fn print_it(s: &str) {
    println!("{}", s);
}

fn main() {
    let cow = Cow::Borrowed("transparent");
    
    // Works via Deref. No as_ref() needed.
    print_it(&cow);
    
    // You can also use it in string methods directly.
    let len = cow.len();
    println!("Length: {}", len);
}

This transparency is huge. You can store Cow in a struct and pass fields to functions expecting &str without any boilerplate. The community relies on this behavior. When you see a function taking Cow<str>, you can assume it plays nice with &str everywhere else.

Convention aside: use Cow::Borrowed explicitly when constructing borrowed variants. Cow::from works, but Cow::Borrowed makes the intent clear. It signals that you are deliberately avoiding allocation.

Pitfalls and compiler errors

Cow is safe, but it has quirks. The compiler will catch mistakes, but the errors can be confusing if you don't know what to expect.

You can't mutate a Cow directly. The enum variants are distinct. If you write cow.push_str("x"), the compiler rejects this with E0596 (cannot borrow as mutable). Cow is immutable by default. You must call to_mut() to get a &mut str. This forces you to acknowledge the potential clone.

Lifetimes matter. Cow<'a, str> means the borrowed variant lives as long as 'a. If you try to return Cow::Borrowed pointing to a local variable, the compiler stops you with E0716 (temporary value dropped while borrowed). The reference would dangle. Cow doesn't extend lifetimes. It just wraps them.

use std::borrow::Cow;

fn bad() -> Cow<str> {
    let s = String::from("local");
    // Error: E0716. The String is dropped at the end of the function.
    // The Cow would hold a dangling reference.
    Cow::Borrowed(&s)
}

Also, into_owned() always allocates if the data is borrowed. It doesn't check if the string is already owned. If you call into_owned() on a Cow::Owned, it just returns the string. If you call it on Cow::Borrowed, it clones. Be careful in hot loops. If you know the result is borrowed, calling into_owned() defeats the purpose.

Cow is a wrapper, not a magic wand. Lifetimes still apply. If the data dies, the Cow dies with it.

Decision matrix

Pick the tool that matches your mutation pattern. Rare writes get Cow. Constant writes get String.

Use Cow<str> when you have a function that accepts string data and might modify it, but modification is rare. Use Cow<str> when you want to return a string from a function and avoid allocation in the common case where the input is already valid. Use Cow<str> when you need to store string data in a struct and want to accept both borrowed and owned inputs without forcing the caller to allocate.

Use &str when you only read the data and never need to mutate it. Use &str when the lifetime is simple and you don't need to return owned data. Use String when you always need ownership and mutation. Use String when the caller is already passing a String and you don't care about the optimization. Use String when profiling shows that Cow's check overhead is significant in your hot path.

Use Box<str> when you need a heap-allocated string slice without the overhead of String's capacity tracking. Use Box<str> when you want to shrink a String to its exact length and store it.

Match the type to the mutation pattern. Rare writes get Cow. Constant writes get String.

Where to go next