What Cow is and why it matters

Cow<'a, B> is an enum in std::borrow with two variants:

  • Borrowed(&'a B)
  • Owned(B::Owned)

The most common form is Cow<'a, str>, but it also works with slices and other borrowed/owned pairs.

The key idea is simple: if you only need read access, you can keep borrowing. If you need to modify the value, Cow can allocate only when mutation becomes necessary.

Typical use cases

Use Cow when:

  • You want an API that accepts both &str and String
  • Most inputs can be returned unchanged
  • Only some inputs need normalization, escaping, filtering, or decoding
  • You want to avoid unnecessary cloning in hot paths

Avoid Cow when:

  • The data is always owned anyway
  • You need frequent mutation regardless of input form
  • The API would be clearer with separate borrowed and owned functions

The core mental model

Think of Cow as a “lazy ownership upgrade.”

You start with a borrowed value when possible. If you need to transform it, Cow can convert to owned data through to_mut() or by constructing an owned variant directly.

A small but important detail: Cow is not magic. It does not eliminate allocations in every case. It simply makes allocation conditional.

Example: normalize a string only if needed

use std::borrow::Cow;

fn normalize_username(input: &str) -> Cow<'_, str> {
    if input.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_') {
        Cow::Borrowed(input)
    } else {
        let normalized = input
            .chars()
            .filter_map(|c| {
                let c = c.to_ascii_lowercase();
                if c.is_ascii_alphanumeric() || c == '_' {
                    Some(c)
                } else {
                    None
                }
            })
            .collect::<String>();

        Cow::Owned(normalized)
    }
}

fn main() {
    let a = normalize_username("alice_42");
    let b = normalize_username("Alice-42!");

    assert_eq!(a, "alice_42");
    assert_eq!(b, "alice42");
}

In this example, already-valid usernames are returned as borrowed data, while invalid ones are normalized into a new String.


When Cow improves API design

A common API design problem is accepting flexible input without forcing callers to allocate.

Consider a function that stores a label, logs a message, or performs validation. If the function takes String, callers with &str must allocate. If it takes &str, callers with owned strings may need to borrow temporarily, and you cannot return owned data easily.

Cow gives you a middle ground.

Accepting flexible input

use std::borrow::Cow;

fn render_tag(label: Cow<'_, str>) -> String {
    format!("<span class=\"tag\">{label}</span>")
}

fn main() {
    let owned = String::from("release");
    let borrowed = "beta";

    let a = render_tag(Cow::Borrowed(borrowed));
    let b = render_tag(Cow::Owned(owned));

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

This works, but it is a bit verbose for callers. In practice, you usually combine Cow with Into<Cow<'a, str>> to make the API ergonomic.

use std::borrow::Cow;

fn render_tag<'a, S>(label: S) -> String
where
    S: Into<Cow<'a, str>>,
{
    let label: Cow<'a, str> = label.into();
    format!("<span class=\"tag\">{label}</span>")
}

fn main() {
    let owned = String::from("release");
    let borrowed = "beta";

    let a = render_tag(borrowed);
    let b = render_tag(owned);

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

This pattern is common in library code because it lets the compiler infer the most efficient representation.


Cow in practice: mutation triggers allocation

The most useful method on Cow is to_mut(). It gives you a mutable reference to the owned data, cloning the borrowed data first if necessary.

Example: append a suffix only when needed

use std::borrow::Cow;

fn ensure_trailing_slash(path: Cow<'_, str>) -> Cow<'_, str> {
    if path.ends_with('/') {
        path
    } else {
        let mut path = path;
        path.to_mut().push('/');
        path
    }
}

fn main() {
    let a = ensure_trailing_slash(Cow::Borrowed("/api"));
    let b = ensure_trailing_slash(Cow::Owned(String::from("/static/")));

    assert_eq!(a, "/api/");
    assert_eq!(b, "/static/");
}

Notice the behavior:

  • If the input already ends with /, no allocation happens.
  • If it does not, to_mut() ensures the value becomes owned before mutation.

This is a good fit for path normalization, query parameter rewriting, and text cleanup.


Choosing the right Cow target type

Cow is generic over a borrowed type B that implements ToOwned. The most common targets are:

TypeBorrowed formOwned formTypical use
Cow<'a, str>&'a strStringText processing, labels, paths
Cow<'a, [T]>&'a [T]Vec<T>Slices, buffers, collections
Cow<'a, Path>&'a PathPathBufFilesystem paths
Cow<'a, OsStr>&'a OsStrOsStringPlatform-native strings

For application code, Cow<'a, str> is by far the most common. For systems and tooling code, Cow<'a, Path> is also very practical.

Example: path handling

use std::borrow::Cow;
use std::path::{Path, PathBuf};

fn normalize_config_path(path: Cow<'_, Path>) -> Cow<'_, Path> {
    if path.is_absolute() {
        path
    } else {
        let mut buf = PathBuf::from("/etc/myapp");
        buf.push(path.as_ref());
        Cow::Owned(buf)
    }
}

This function keeps absolute paths borrowed, but allocates a new PathBuf for relative paths.


Best practices for API authors

Cow is powerful, but it should be used deliberately. The goal is not to replace every &str or String parameter. The goal is to reduce friction and avoid unnecessary copies where it matters.

Prefer Into<Cow<'a, str>> for flexible inputs

If your function consumes the value or may need to normalize it, accepting Into<Cow<'a, str>> is often the most ergonomic option.

This allows callers to pass:

  • &'a str
  • String
  • Cow<'a, str>

Return Cow only when the caller benefits

Returning Cow is useful when the result may be borrowed from input or newly allocated. This is common in:

  • sanitizers
  • parsers
  • canonicalizers
  • lookup functions that may return a direct slice from an existing buffer

If the function always allocates, returning String is clearer.

Keep lifetimes simple

Cow does not remove lifetime complexity; it only packages it more conveniently. If your API has multiple borrowed inputs, make sure the lifetime relationships are obvious.

Avoid overusing Cow in internal code

Inside a function, converting everything to Cow can add noise. If the data is already owned and will stay owned, use String or Vec<T> directly.


Common pitfalls

1. Assuming Cow avoids all cloning

Cow only avoids cloning when the value can remain borrowed. If you call to_mut(), clone may happen immediately.

2. Returning borrowed data from temporary sources

You cannot return Cow::Borrowed from a temporary string created inside the function. The borrowed data must outlive the returned Cow.

3. Using Cow when ownership is already required

If your function stores data in a struct for long-term use, taking String may be simpler and more explicit.

4. Forgetting that Cow is generic

Cow<'a, str> is common, but Cow also works with slices and paths. The same design pattern applies across many domains.


A realistic example: parsing a header value

Suppose you are parsing a header-like value that may need trimming and lowercasing only if it contains uppercase letters or extra whitespace.

use std::borrow::Cow;

fn canonicalize_header_value(input: &str) -> Cow<'_, str> {
    let trimmed = input.trim();

    if trimmed == input && trimmed.chars().all(|c| !c.is_ascii_uppercase()) {
        Cow::Borrowed(input)
    } else {
        Cow::Owned(trimmed.to_ascii_lowercase())
    }
}

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

    assert_eq!(a, "content-type");
    assert_eq!(b, "content-type");
}

This is a practical pattern for request processing, configuration parsing, and protocol adapters. The function preserves the original input when possible and allocates only when normalization is required.


Cow versus alternatives

ApproachProsConsBest for
&strSimple, zero allocationCannot own or return transformed data easilyRead-only APIs
StringEasy ownership and mutationForces allocation for borrowed inputsStored or heavily mutated text
Cow<'a, str>Flexible, allocates only when neededSlightly more complex APIConditional transformation
impl AsRef<str>Very ergonomic for read-only accessNot suitable when ownership may be neededInput-only functions

A useful rule of thumb: use AsRef for read-only access, String for always-owned data, and Cow when the function may need to return or mutate data conditionally.


Performance considerations

Cow can reduce allocations, but performance depends on the workload.

It is most effective when:

  • Most inputs are already in the desired form
  • Transformations are rare
  • Borrowed data is common in hot paths

It may be less effective when:

  • Nearly every input requires mutation
  • The borrowed/owned branching adds complexity without measurable benefit
  • The code becomes harder to read than a direct String implementation

Always measure if performance is the main motivation. Cow is best justified by API flexibility and predictable allocation behavior, not by assumptions.


Conclusion

Cow is one of Rust’s most practical abstraction tools for API design. It lets you accept borrowed or owned data through a single interface, preserve zero-copy behavior when possible, and allocate only when a transformation actually requires it.

Use it when your function sometimes needs ownership but often can work with borrowed input. In those cases, Cow gives you a clean balance of ergonomics, performance, and correctness.

Learn more with useful resources