What is the Cow type in Rust

`Cow` (Clone-on-Write) is a smart pointer that allows you to borrow data cheaply but provides a fallback to owning and mutating that data only when necessary.

What is the Cow type in Rust

You're writing a function to sanitize user input. It takes a string, checks for uppercase letters, and returns the lowercase version. Most of the time, the input is already lowercase. You just pass it through. Sometimes, the input has caps. You need to clone the string, fix it, and return the new version.

If you always return String, you allocate memory and copy data even when the input is perfect. That wastes CPU and memory on the common case. If you only accept &str and return &str, you can't return a modified string because the caller didn't give you ownership. You're stuck between two bad choices. Cow breaks the deadlock.

Cow stands for Clone-on-Write. It is an enum that holds either a borrowed reference or an owned value. It lets you write one function that handles both cases efficiently. You borrow the data cheaply. The moment you need to mutate it, Cow clones the data into an owned version behind the scenes. You pay the cost only when you actually change something.

Concept and analogy

Think of a digital photo viewer. You have a thumbnail on your screen. Clicking it shows the full image. If you just look at the image, the viewer is displaying the original file from disk. No copy is made. If you decide to edit the photo, the software silently creates a private copy in memory. You work on the copy, and the original file stays untouched.

Cow works the same way. It points to the original data. As long as you only read, it behaves like a reference. The instant you try to modify it, Cow creates a copy for you to work on. The API doesn't change. You call the same methods. The difference is hidden inside the enum.

Cow<'a, T> has two variants:

  • Borrowed(&'a T) holds a reference to existing data.
  • Owned(T) holds a value you own.

The lifetime 'a ties the borrowed case to the input data. The owned case ignores the lifetime because it owns its memory. Cow implements Deref<Target = T>, so you can use it like a reference in most contexts. The compiler coerces Cow to &T automatically.

Minimal example

Here is a function that normalizes text. It returns a Cow so the caller gets a reference if no changes are needed, or an owned string if mutation happened.

use std::borrow::Cow;

/// Returns a normalized string, borrowing if possible, owning if mutated.
fn normalize(input: &str) -> Cow<'_, str> {
    if input.is_ascii_lowercase() {
        // Input is already good. Return a reference to avoid allocation.
        Cow::Borrowed(input)
    } else {
        // Input needs changes. Clone to String, mutate, return owned.
        let mut owned = input.to_string();
        owned.make_ascii_lowercase();
        Cow::Owned(owned)
    }
}

fn main() {
    let text = "hello";
    // No allocation happens here. The result points to `text`.
    let result = normalize(text);
    println!("Len: {}", result.len()); // Deref coercion allows .len()

    let mixed = "Hello";
    // Allocation happens here. The result owns a new String.
    let result2 = normalize(mixed);
    println!("Result: {}", result2);
}

The Cow behaves like &str when you call len() or println!. The compiler sees Cow and applies Deref coercion to treat it as a reference. You don't need to check which variant is active to read the data.

Convention aside: Cow::clone() clones the Cow, not necessarily the inner data. If the Cow is Borrowed, cloning it is cheap because it just copies the reference. If the Cow is Owned, cloning it clones the inner value. This matches the behavior of the inner type. Don't assume Cow::clone() is always free.

Walkthrough: compile and runtime

At compile time, Cow is just an enum. The compiler knows it can hold a reference or a value. When you return Cow::Borrowed(input), the compiler checks that input lives long enough. The lifetime 'a in Cow<'a, str> ensures the reference is valid. When you return Cow::Owned(s), the lifetime doesn't matter for the data, but the type signature still carries 'a. This allows the function to return either variant with the same type.

At runtime, Cow has no overhead for the borrowed case. Cow::Borrowed stores a pointer. Calling methods via Deref just follows that pointer. When you switch to Owned, the memory is allocated on the heap. The transition happens explicitly when you call Cow::Owned or use methods like to_mut().

Cow does not auto-clone on mutation. This is a common misconception. If you have a Cow::Borrowed and try to mutate it directly, the compiler rejects you. You must call to_mut() or into_owned() to trigger the clone. Cow is lazy. It waits for you to ask for mutation before paying the cost.

Realistic example

Consider a function that processes a list of tags. It trims whitespace and lowercases each tag. Most tags are already clean. Some need fixing. Using Cow avoids cloning the clean tags.

use std::borrow::Cow;

/// Processes tags: trims and lowercases. Returns Cow to avoid unnecessary clones.
fn clean_tags(tags: &[&str]) -> Vec<Cow<'_, str>> {
    tags.iter().map(|tag| {
        let trimmed = tag.trim();
        if trimmed == tag && trimmed.is_ascii_lowercase() {
            // Tag is pristine. Keep the reference.
            Cow::Borrowed(tag)
        } else {
            // Tag needs work. Clone and fix.
            let mut owned = trimmed.to_string();
            owned.make_ascii_lowercase();
            Cow::Owned(owned)
        }
    }).collect()
}

fn main() {
    let raw_tags = &["rust", "  Rust  ", "faq"];
    let cleaned = clean_tags(raw_tags);
    
    for tag in &cleaned {
        println!("{}", tag);
    }
}

The function returns Vec<Cow<'_, str>>. The caller can iterate over the result and use each tag as &str. If the caller needs to store the tags long-term, they can call into_owned() on each Cow to get Strings. The Cow gives the caller flexibility.

Convention aside: Cow is often used at API boundaries. Library authors return Cow to let users decide when to clone. Inside a module, it's usually better to use &str or String directly. Cow adds indirection. Use it where the performance benefit outweighs the complexity.

Pitfalls and compiler errors

Cow has a few traps. The biggest one is trying to mutate a Borrowed Cow without calling the right method. If you have let mut cow = Cow::Borrowed("hi"); and try cow.push_str("x");, the compiler rejects you. Cow does not implement DerefMut. You can't mutate through a reference. You must call cow.to_mut().push_str("x");. to_mut() checks the variant. If it's Borrowed, it clones to Owned and returns &mut String. If it's Owned, it returns &mut String directly.

If you forget to make the Cow mutable, you get E0596 (cannot borrow as mutable). The compiler tells you the variable is immutable. Add mut to the binding.

use std::borrow::Cow;

fn main() {
    let cow = Cow::Borrowed("hi");
    // Error E0596: cannot borrow as mutable, as it is not declared mutable
    // cow.to_mut().push_str("x"); 
}

Another pitfall is lifetime confusion. Cow<'a, str> ties the borrowed case to 'a. If you return Cow::Owned, the lifetime is irrelevant for the data, but the type still carries 'a. This can cause issues when storing Cow in structs. If you put Cow<'a, str> in a struct, the struct gets a lifetime parameter. This limits how you can use the struct. Often, it's better to store String in structs and convert to Cow only for function arguments or return types.

If you try to return a Cow::Borrowed pointing to a local variable, you get E0515 (cannot return value referencing local data). The reference would dangle. You must return Cow::Owned in that case.

fn bad_cow() -> Cow<'_, str> {
    let local = String::from("hello");
    // Error E0515: cannot return value referencing local variable `local`
    // Cow::Borrowed(&local)
    Cow::Owned(local) // Correct: own the data
}

Cow with slices has a subtle limitation. Cow<'a, [T]> allows element mutation but not structural mutation. to_mut() returns &mut [T], not &mut Vec<T>. You can change elements, but you can't push or pop. If you need to change the length, call into_owned() to get a Vec<T>. This distinction matters when processing arrays.

Convention aside: Cow::Borrowed and Cow::Owned naming is historical. Some developers wish it was Ref and Own. The names are fixed. Stick with Borrowed and Owned. They clearly describe the ownership state.

Decision: when to use Cow

Use Cow<'a, str> when your function accepts text that might need modification, and you want to avoid cloning if the text is already in the correct form. Use &str when your function only reads the data and never needs to return a modified version. Use String when your function always transforms the data, or when the caller must own the result regardless of input. Use Cow<'a, [T]> when processing slices where most elements are already valid, but some require element-wise transformation. Use Vec<T> when you need to add or remove elements from the collection. Use Rc<str> when multiple owners need to share the same string data across different scopes, and mutation is not required.

Pick the type that matches your mutation pattern. If you never mutate, Cow is overhead. If you always mutate, Cow is indirection. Use it when mutation is the exception.

Where to go next