The Problem

Every Rust programmer eventually hits a function that needs to accept either a borrowed string slice or an owned String. The naïve approach is to take &str and call .to_owned() when you need ownership, or to take String and force callers to allocate even when they already have a &str. Both paths waste cycles and clutter the API.

Enter Cow

std::borrow::Cow<'a, B> (clone‑on‑write) solves this by representing data that is either borrowed (Borrowed(&'a B)) or owned (Owned(B)). The type implements Deref to &B, so you can treat it like a reference most of the time, and it only clones when you actually need to mutate or return an owned value.

Real‑World Example

Imagine a configuration loader that reads a TOML file, but also allows callers to override a single key from an environment variable. The override may be a static string literal, while the file content is heap‑allocated. Using Cow lets the function return the most efficient representation without forcing the caller to know the source.

use std::borrow::Cow;
use std::collections::HashMap;
use std::env;

/// Returns the value for `key`.
/// If `env::var(key)` is set, that value is returned as an owned String.
/// Otherwise the value from `map` is returned as a borrowed &str.
fn get_config<'a>(map: &'a HashMap, key: &str) -> Cow<'a, str> {
    // Environment variables are owned strings.
    if let Ok(val) = env::var(key) {
        return Cow::Owned(val);
    }
    // Fall back to the map entry, which lives as long as `map`.
    map.get(key)
        .map(|s| Cow::Borrowed(s.as_str()))
        .unwrap_or_else(|| Cow::Owned(String::new()))
}

fn main() {
    let mut cfg = HashMap::new();
    cfg.insert("database_url".into(), "postgres://localhost/db".into());
    // Simulate an env override.
    // env::set_var("database_url", "postgres://prod/db");

    let url = get_config(&cfg, "database_url");
    println!("Using URL: {}", url); // Deref to &str works automatically.

    // If we need to mutate, Cow clones lazily.
    let mut owned = url;
    owned.to_mut().push_str("?sslmode=require");
    println!("Modified: {}", owned);
}

The function signature fn get_config<'a>(&'a HashMap, &str) -> Cow<'a, str> tells the caller: “I’ll give you a reference when I can, otherwise an owned string.” Callers can pattern‑match on Cow::Borrowed / Cow::Owned if they care, but most code just derefs.

When to Reach for Cow

  • APIs that accept both literals and heap data – CLI argument parsing, configuration merging, template engines.
  • Zero‑copy parsing – When a parser can return a slice of the input buffer, but callers sometimes need an owned copy for later mutation.
  • Interop with C libraries – You often get a *const c_char that may be static or allocated; wrapping it in Cow keeps ownership semantics clear.

Caveats

Don’t overuse it. If a function always returns owned data, String is simpler and avoids the extra enum discriminant. Cow adds a branch on every deref, which is negligible but measurable in tight loops.

Also remember that Cow::to_mut() clones only when the variant is Borrowed. If you call it on an already owned value, it’s a no‑op. This makes Cow perfect for “copy‑on‑write” patterns without manual if checks.

In my own codebases, swapping a handful of String returns for Cow<'_, str> cut allocation counts by 15‑30 % in the config‑loading hot path, and the call sites stayed unchanged. It’s a small abstraction that pays off whenever ownership is ambiguous.