What Cow solves

Cow stands for “clone on write.” It represents data in one of two forms:

  • Borrowed(&'a T) — no allocation, just a reference
  • Owned(T) — an owned value, usually created only when mutation or transformation is needed

In Rust, Cow is most useful when:

  • a function accepts input that is usually passed through unchanged
  • a transformation is needed only for a subset of cases
  • cloning would be expensive or frequent
  • you want a single API that supports both borrowed and owned callers

A common example is string normalization. If the input is already valid and usable, return a borrowed value. If it needs cleanup, return an owned string.

Why Cow can improve performance

The main performance benefit is avoiding unnecessary allocation and copying. Consider a pipeline that processes thousands of strings:

  • most strings are already in the desired form
  • a small fraction need trimming, escaping, or case normalization

Without Cow, you might allocate a new String for every item. With Cow, you can return borrowed data for the common case and allocate only for the exceptional case.

That said, Cow is not a universal optimization. It adds some branching and type complexity, so it is best when the “borrowed most of the time” pattern is real and measurable.

Cow basics

Cow is generic over a borrowed type and requires that type to support cloning into an owned form. For strings, the standard type is:

use std::borrow::Cow;

fn maybe_normalize(input: &str) -> Cow<'_, str> {
    if input.chars().all(|c| c.is_ascii_lowercase()) {
        Cow::Borrowed(input)
    } else {
        Cow::Owned(input.to_ascii_lowercase())
    }
}

This function returns a borrowed &str when the input is already lowercase ASCII, and an owned String otherwise.

Key methods

A few methods matter most in performance-oriented code:

  • Cow::Borrowed(value) — construct a borrowed variant
  • Cow::Owned(value) — construct an owned variant
  • cow.into_owned() — convert to owned data, cloning only if needed
  • cow.to_mut() — get mutable access, cloning if currently borrowed
  • cow.as_ref() — borrow as &T regardless of variant

A practical example: request header normalization

Suppose you are building a lightweight HTTP component that normalizes header names. Most incoming headers are already lowercase, but some clients send mixed case. You want to avoid allocating for the common case.

use std::borrow::Cow;

fn normalize_header_name(name: &str) -> Cow<'_, str> {
    if name.bytes().all(|b| b.is_ascii_lowercase() || b == b'-') {
        Cow::Borrowed(name)
    } else {
        Cow::Owned(name.to_ascii_lowercase())
    }
}

fn main() {
    let a = normalize_header_name("content-type");
    let b = normalize_header_name("Content-Type");

    assert!(matches!(a, Cow::Borrowed(_)));
    assert!(matches!(b, Cow::Owned(_)));

    println!("{a}");
    println!("{b}");
}

This pattern is useful because it keeps the fast path allocation-free. If your code later needs an owned string, call into_owned() at the boundary where ownership is required.

When Cow is a good fit

The best use cases are APIs where the caller may already own or borrow the data, and the callee may or may not need to modify it.

ScenarioUse Cow?Reason
Pass-through data with rare normalizationYesBorrowed fast path avoids allocation
Always mutate the inputNoOwned String is simpler and often faster
Frequently clone regardless of branchNoCow adds complexity without benefit
API accepts both &str and StringOftenCow<'_, str> can unify the interface
Temporary transformation in a hot loopMaybeBenchmark first; branch cost may matter

Designing APIs with Cow

A common performance-oriented API design is to accept impl Into<Cow<'a, str>> or return Cow<'a, str>. This gives callers flexibility without forcing allocation.

Accepting flexible input

use std::borrow::Cow;

fn store_label<'a>(label: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
    let label = label.into();

    if label.len() > 32 {
        Cow::Owned(label.chars().take(32).collect())
    } else {
        label
    }
}

This function accepts either borrowed or owned input. If the label is short enough, it returns the original value unchanged. If it is too long, it creates a shortened owned string.

Returning Cow from transformation functions

Returning Cow is especially useful when a function may preserve input unchanged:

use std::borrow::Cow;

fn strip_prefix_if_present<'a>(s: &'a str, prefix: &str) -> Cow<'a, str> {
    if let Some(rest) = s.strip_prefix(prefix) {
        Cow::Borrowed(rest)
    } else {
        Cow::Borrowed(s)
    }
}

This example is zero-allocation in both branches because the result is always a slice into the original string. If a transformation requires a new buffer, switch to Cow::Owned.

Mutating with to_mut

The to_mut() method is the key to Cow’s “clone on write” behavior. It gives you mutable access, cloning the borrowed data only when mutation is needed.

use std::borrow::Cow;

fn ensure_suffix<'a>(mut s: Cow<'a, str>, suffix: &str) -> Cow<'a, str> {
    if !s.ends_with(suffix) {
        s.to_mut().push_str(suffix);
    }
    s
}

If s is borrowed and already has the suffix, no allocation occurs. If it needs modification, to_mut() clones into an owned String first.

Best practice

Use to_mut() only when mutation is conditional. If you know you will always mutate, start with an owned String instead. That avoids an extra branch and makes intent clearer.

Cow for collections

Cow is not limited to strings. It also works with slices, which is useful for read-mostly data structures.

use std::borrow::Cow;

fn maybe_filter_ids<'a>(ids: &'a [u32]) -> Cow<'a, [u32]> {
    if ids.len() <= 4 {
        Cow::Borrowed(ids)
    } else {
        Cow::Owned(ids.iter().copied().filter(|id| id % 2 == 0).collect())
    }
}

This pattern can be effective when:

  • small inputs can be passed through unchanged
  • larger inputs need filtering, sorting, or deduplication
  • you want to avoid allocating for the common case

For slices, the owned form is usually Vec<T>.

Avoiding common mistakes

1. Using Cow when ownership is always required

If a function always needs to store, mutate, or extend the data, Cow often adds overhead without benefit. For example, if you always append to a string, take String directly.

2. Returning Cow from deeply internal hot loops

Cow is excellent at API boundaries, but inside a tight loop it may introduce branching and inhibit simpler optimizations. In inner loops, prefer a concrete type if the data flow is already known.

3. Calling into_owned() too early

If you immediately convert every Cow into an owned value, you lose the main advantage. Delay ownership conversion until the point where it is truly needed.

4. Overusing Cow for readability

Cow is a performance tool, not a default abstraction. If the borrowed/owned distinction does not matter, a plain &str or String is often easier to maintain.

Benchmarking before and after

Because Cow trades allocations for branching and type complexity, benchmark your actual workload. A microbenchmark should compare:

  • always allocating
  • borrowed fast path with Cow
  • concrete owned type

A simple benchmark scenario might normalize 1 million header names with a 95/5 split between already-normalized and mixed-case inputs. In that case, Cow often wins because it avoids most allocations. But if 80% of inputs need modification, the benefit may shrink or disappear.

When benchmarking, watch for:

  • allocation count
  • total bytes allocated
  • branch misprediction effects
  • end-to-end latency, not just isolated function time

Practical guidelines

Use Cow when the following are true:

  • the input is often already in usable form
  • allocation is a measurable cost
  • you need an API that supports both borrowed and owned callers
  • mutation is conditional, not guaranteed

Prefer a concrete owned type when:

  • the value will always be modified
  • the function must store the data long-term
  • the borrowed case is rare
  • simplicity matters more than saving allocations

A decision checklist

Before introducing Cow, ask:

  1. Can the fast path remain borrowed?
  2. Is the cost of cloning or allocating significant?
  3. Will the API be easier to use with a single flexible return type?
  4. Can you delay ownership conversion until the boundary?
  5. Have you benchmarked the real workload?

If the answer to most of these is yes, Cow is likely a good fit.

Summary

Cow is a practical Rust optimization tool for reducing unnecessary allocation in read-mostly workflows. It shines when data is usually borrowed, but occasionally needs transformation. By returning borrowed values on the fast path and cloning only on demand, you can keep APIs flexible without paying for ownership unless it is actually needed.

Used carefully, Cow can make performance-sensitive Rust code both efficient and ergonomic.

Learn more with useful resources