Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1.2 · Borrow, ToOwned, and Cow: Flexible Ownership Boundaries

Domain 1 — Ownership Mechanics in the Standard Library Duration: ~15 minutes Library components: std::borrow::Borrow, std::borrow::BorrowMut, std::borrow::ToOwned, std::borrow::Cow

Introduction

The ownership model of Rust gives you safety guarantees. It also causes a practical problem: a function frequently must accept data in borrowed form and in owned form. For example, you must select between a &str parameter and a String parameter, or between &[u8] and Vec<u8>.

The std::borrow module has three tools that solve this problem:

  • Borrow<T> gives you a &T. The trait promises that the &T hashes and compares the same as the original value.
  • ToOwned makes the related owned value from a borrowed value.
  • Cow<'a, B> contains borrowed data or owned data. The program selects the variant at runtime, and Cow allocates only when it is necessary.

This tutorial explains how each tool operates and when to use it. It also explains how Cow removes a common class of unnecessary allocations.

Borrow: The Hash/Eq Contract

The definition of the Borrow trait is short:

// `?Sized` permits unsized targets such as `str` and `[T]`.
pub trait Borrow<Borrowed: ?Sized> {
    fn borrow(&self) -> &Borrowed;
}

String implements Borrow<str>. Vec<T> implements Borrow<[T]>. AsRef also converts references. The difference between the two traits is a semantic contract. The compiler does not enforce this contract, but each implementor must obey it:

If T: Borrow<Q>, then t.borrow() must give a value that hashes identically and compares equally to t.

HashMap::get depends on this contract. When you call .get("some_key") on a HashMap<String, V>, the map hashes your &str. Then it searches for an equal String key. If the Borrow<str> implementation of String did not keep the hash and the equality consistent, lookups would silently fail.

Use case: Looking up HashMap entries with &str keys

use std::borrow::Borrow;
use std::collections::HashMap;

fn main() {
    let mut scores: HashMap<String, u32> = HashMap::new();
    scores.insert(String::from("Alice"), 100);
    scores.insert(String::from("Bob"), 85);

    // HashMap::get accepts a &Q for each Q where String: Borrow<Q>.
    // String: Borrow<str>, so a &str is a valid key. You do not need a String.
    let alice_score = scores.get("Alice"); // Option<&u32>
    println!("Alice's score: {alice_score:?}");
    assert_eq!(alice_score, Some(&100));

    // A generic function with the same bounds as HashMap::get.
    fn find_score<Q>(map: &HashMap<String, u32>, key: &Q) -> Option<u32>
    where
        String: Borrow<Q>,
        Q: std::hash::Hash + Eq + ?Sized, // ?Sized permits Q = str
    {
        map.get(key).copied() // Option<&u32> becomes Option<u32>
    }

    let s1 = find_score(&scores, "Alice");      // key is &str: Q = str
    let owned_key = String::from("Bob");
    let s2 = find_score(&scores, &owned_key);   // key is &String: Q = String
    println!("find_score(&str):    {s1:?}");
    println!("find_score(&String): {s2:?}");
    assert_eq!(s1, Some(100));
    assert_eq!(s2, Some(85));

    // Check the hash/eq contract.
    use std::hash::{DefaultHasher, Hash, Hasher};

    // Returns the hash of one value. ?Sized permits T = str.
    fn compute_hash<T: Hash + ?Sized>(val: &T) -> u64 {
        let mut h = DefaultHasher::new();
        val.hash(&mut h);
        h.finish()
    }

    let owned = String::from("rust");
    let borrowed: &str = owned.borrow();
    let h1 = compute_hash(&owned);   // the hash of the String
    let h2 = compute_hash(borrowed); // the hash of the str
    println!("Hash of String 'rust': {h1}");
    println!("Hash of &str 'rust':   {h2}");
    assert_eq!(h1, h2, "Borrow contract: hashes must match");
}

01_07_borrow_trait.rs prints these lines for the code above. The two hash values are always equal, but the value can change between Rust releases:

Alice's score: Some(100)
find_score(&str):    Some(100)
find_score(&String): Some(85)
Hash of String 'rust': 18297823557882888176   # (can change between Rust releases)
Hash of &str 'rust':   18297823557882888176   # (can change between Rust releases)

The important difference from AsRef: implement Borrow<Q> for your type only if you can guarantee that the borrowed form hashes and compares identically. Tutorial 1.3 explains when AsRef is the correct selection.

ToOwned: From Borrowed to Owned

ToOwned is the inverse of Borrow. Borrow goes from owned to borrowed. ToOwned goes from borrowed to owned:

pub trait ToOwned {
    // The owned type. It must be able to give a borrow of `Self` back.
    type Owned: Borrow<Self>;
    fn to_owned(&self) -> Self::Owned;
    // (The trait also has the provided method `clone_into`.)
}

The standard library has these implementations:

Borrowed typeto_owned() returns
strString
[T]Vec<T>
PathPathBuf
OsStrOsString
CStrCString

Why Clone is not sufficient

Clone on a reference type clones the reference, not the data behind the reference. A clone of a &str gives you a second &str. ToOwned gives you a String.

Use case: Going from borrowed to owned across type boundaries

use std::borrow::Borrow;
use std::borrow::ToOwned;
use std::path::Path;

fn main() {
    // &str → String
    let greeting: &str = "hello";
    let owned: String = greeting.to_owned(); // allocates and copies the bytes
    println!("borrowed: {greeting:?}");
    println!("owned:    {owned:?}");
    assert_eq!(greeting, owned);

    // Contrast: Clone on a &str gives a second &str, not a String.
    // `greeting.clone()` is the same call, but the compiler warns that it does nothing.
    let cloned: &str = Clone::clone(&greeting);
    println!("cloned (still &str): {cloned:?}");

    // &[i32] → Vec<i32>
    println!("\n--- Slice to Vec ---");
    let slice: &[i32] = &[1, 2, 3, 4, 5];
    let vec: Vec<i32> = slice.to_owned();
    println!("slice: {slice:?}");
    println!("vec:   {vec:?}");
    assert_eq!(slice, vec.as_slice());

    // &Path → PathBuf
    println!("\n--- Path to PathBuf ---");
    let path: &Path = Path::new("/usr/local/bin");
    let path_buf: std::path::PathBuf = path.to_owned();
    println!("path:     {}", path.display());
    println!("path_buf: {}", path_buf.display());
    assert_eq!(path, path_buf);

    // Round trip: to_owned makes the String, borrow gives a &str back.
    println!("\n--- ToOwned ↔ Borrow round-trip ---");
    let original: &str = "round trip";
    let owned: String = original.to_owned();
    let back: &str = owned.borrow();
    println!("original:  {original:?}");
    println!("owned:     {owned:?}");
    println!("back:      {back:?}");
    assert_eq!(original, back);

    println!("\nAll assertions passed.");
}

01_08_to_owned.rs prints:

borrowed: "hello"
owned:    "hello"
cloned (still &str): "hello"

--- Slice to Vec ---
slice: [1, 2, 3, 4, 5]
vec:   [1, 2, 3, 4, 5]

--- Path to PathBuf ---
path:     /usr/local/bin
path_buf: /usr/local/bin

--- ToOwned ↔ Borrow round-trip ---
original:  "round trip"
owned:     "round trip"
back:      "round trip"

All assertions passed.

Figure: ToOwned and Borrow — The Ownership Round-Trip

Cow: Clone-on-Write

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

pub enum Cow<'a, B: ?Sized + ToOwned> {
    Borrowed(&'a B),              // a reference to the data: no allocation
    Owned(<B as ToOwned>::Owned), // the owned type: String when B is str
}

Thus a Cow<'a, str> is a &'a str or a String. A Cow<'a, [T]> is a &'a [T] or a Vec<T>.

The advantage of Cow is deferred allocation. You start with borrowed data. If you never mutate the data, you never allocate. If you must mutate the data, call .to_mut(). It clones the data into the owned variant and gives you a mutable reference.

Use case: A text function that usually returns the input unchanged

This is the standard use of Cow. A function sometimes must change its input, but usually it does not. Without Cow, you have two alternatives. You can always allocate a new String, which is wasteful. Or you can pass lifetimes through your API in complex ways.

use std::borrow::Cow;

fn main() {
    // Returns the input unchanged, or a changed copy of the input.
    fn remove_profanity(input: &str) -> Cow<'_, str> {
        if input.contains("darn") {
            // A change is necessary: `replace` allocates a new String.
            Cow::Owned(input.replace("darn", "****"))
        } else {
            // No change is necessary: borrow the input, no allocation.
            Cow::Borrowed(input)
        }
    }

    let clean = "have a nice day";
    let dirty = "oh darn, that's broken";

    let result1 = remove_profanity(clean); // Cow::Borrowed
    let result2 = remove_profanity(dirty); // Cow::Owned

    // matches! returns true when the value has the given variant.
    println!("clean input → {:?} (borrowed: {})", result1, matches!(result1, Cow::Borrowed(_)));
    println!("dirty input → {:?} (borrowed: {})", result2, matches!(result2, Cow::Borrowed(_)));

    assert!(matches!(result1, Cow::Borrowed(_)));
    assert!(matches!(result2, Cow::Owned(_)));
    assert_eq!(&*result2, "oh ****, that's broken"); // &* derefs the Cow to a &str
}

01_09_cow_basics.rs prints these lines for the code above:

clean input → "have a nice day" (borrowed: true)
dirty input → "oh ****, that's broken" (borrowed: false)

Some programs process millions of log lines, and only a small fraction of the lines need a change. For such a program, this pattern prevents millions of unnecessary allocations.

Cow with slices

Cow accepts each type that implements ToOwned, and slices are such a type:

use std::borrow::Cow;

fn main() {
    // Returns the input if it is sorted, or a sorted copy of the input.
    fn ensure_sorted(data: &[i32]) -> Cow<'_, [i32]> {
        // windows(2) gives each pair of adjacent elements.
        if data.windows(2).all(|w| w[0] <= w[1]) {
            Cow::Borrowed(data) // already sorted: no allocation
        } else {
            let mut owned = data.to_vec(); // copies the slice into a new Vec
            owned.sort_unstable();
            Cow::Owned(owned)
        }
    }

    let sorted = [1, 2, 3, 4, 5];
    let unsorted = [5, 3, 1, 4, 2];

    let r1 = ensure_sorted(&sorted);   // Cow::Borrowed
    let r2 = ensure_sorted(&unsorted); // Cow::Owned

    println!("already sorted → borrowed: {}", matches!(r1, Cow::Borrowed(_)));
    println!("unsorted       → borrowed: {}", matches!(r2, Cow::Borrowed(_)));
    assert_eq!(&*r2, &[1, 2, 3, 4, 5]); // r2 contains a sorted copy
}

01_09_cow_basics.rs prints these lines for the code above:

already sorted → borrowed: true
unsorted       → borrowed: false

Cow in API Design

Cow is most useful together with Into<Cow<'a, str>> in function signatures. &str and String each implement Into<Cow<str>>. Thus your API accepts the two types, and the caller does not have to select one.

Use case: A configuration struct that avoids allocating for literal keys

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

#[derive(Debug)]
struct Config<'a> {
    // Each key and each value is borrowed data or owned data.
    entries: HashMap<Cow<'a, str>, Cow<'a, str>>,
}

impl<'a> Config<'a> {
    fn new() -> Self {
        Config { entries: HashMap::new() }
    }

    // A &'a str becomes Cow::Borrowed. A String becomes Cow::Owned.
    fn set(&mut self, key: impl Into<Cow<'a, str>>, value: impl Into<Cow<'a, str>>) {
        self.entries.insert(key.into(), value.into());
    }

    // Cow<str> implements Borrow<str>, so a &str is a valid lookup key.
    fn get(&self, key: &str) -> Option<&str> {
        self.entries.get(key).map(|v| v.as_ref()) // &Cow<str> becomes &str
    }
}

fn main() {
    let mut config = Config::new();

    // String literals become Cow::Borrowed: no allocation.
    config.set("host", "localhost");
    config.set("port", "8080");

    // A String that the program makes at runtime becomes Cow::Owned.
    let user = std::env::var("USER").unwrap_or_else(|_| "anonymous".to_string());
    config.set("user", user);

    // A literal key with a runtime value.
    let db_url = format!("postgres://{}@localhost/mydb", config.get("user").unwrap());
    config.set("database_url", db_url);

    println!("Config entries:");
    for (k, v) in &config.entries {
        let kind = if matches!(v, Cow::Borrowed(_)) { "borrowed" } else { "owned" };
        println!("  {k} = {v} ({kind})");
    }

    assert_eq!(config.get("host"), Some("localhost"));
    assert_eq!(config.get("port"), Some("8080"));
}

01_10_cow_api_design.rs prints these lines for the code above. The order of the four entries changes between runs, because the iteration order of a HashMap is not specified. The user name is the value of the USER environment variable (here, admin):

Config entries:
  host = localhost (borrowed)   # (order varies)
  user = admin (owned)   # (varies with USER)
  port = 8080 (borrowed)
  database_url = postgres://admin@localhost/mydb (owned)   # (varies with USER)

Cow Mutation: to_mut and into_owned

Two methods control the change from borrowed to owned:

  • to_mut(&mut self) -> &mut B::Owned: If the Cow is borrowed, the method clones the data into the owned variant. Then it returns a mutable reference. If the Cow is already owned, the method only returns the mutable reference. This clone is the "clone-on-write" step.
  • into_owned(self) -> B::Owned: The method consumes the Cow. If the Cow is borrowed, the method clones the data. If the Cow is already owned, the method returns the owned value and does not clone.

Use case: Conditionally mutating borrowed data

use std::borrow::Cow;

fn main() {
    let mut cow: Cow<str> = Cow::Borrowed("immutable");
    println!("Before to_mut: {:?} (borrowed: {})", cow, matches!(cow, Cow::Borrowed(_)));

    // The first to_mut call clones "immutable" into a String.
    let mutable_ref = cow.to_mut(); // &mut String
    mutable_ref.push_str(" → now mutable!");
    println!("After to_mut:  {:?} (borrowed: {})", cow, matches!(cow, Cow::Borrowed(_)));
    assert!(matches!(cow, Cow::Owned(_)));

    // The second to_mut call does NOT clone: the Cow is already owned.
    println!("\n--- Second to_mut: no extra clone ---");
    let mutable_ref = cow.to_mut();
    mutable_ref.push_str(" (still the same allocation)");
    println!("After second to_mut: {cow:?}");

    // into_owned consumes the Cow and returns the owned value.
    println!("\n--- into_owned ---");
    let cow1: Cow<str> = Cow::Borrowed("borrow me");
    let cow2: Cow<str> = Cow::Owned(String::from("own me"));

    let s1: String = cow1.into_owned(); // borrowed: clones the data
    let s2: String = cow2.into_owned(); // owned: moves the String, no clone
    println!("s1 = {s1:?}");
    println!("s2 = {s2:?}");
}

01_11_cow_mutation.rs prints these lines for the code above:

Before to_mut: "immutable" (borrowed: true)
After to_mut:  "immutable → now mutable!" (borrowed: false)

--- Second to_mut: no extra clone ---
After second to_mut: "immutable → now mutable! (still the same allocation)"

--- into_owned ---
s1 = "borrow me"
s2 = "own me"

Figure: Cow State Transitions

When to Use Each

SituationUse
Collection lookups (HashMap::get)Borrow<T> (the hash/eq contract is necessary)
Conversion of &str to String, or of &[T] to Vec<T>ToOwned
A function that usually returns its input unchangedCow<T> (deferred allocation)
An API that accepts &str and Stringimpl Into<Cow<str>>, or the simpler impl AsRef<str> if you only read the data
A processing pipeline with conditional mutationCow<T> with to_mut()

A common mistake is to use Cow when a &str parameter is sufficient. Cow adds complexity. Use it when you have measured evidence that deferred allocation is important for your workload. Also use it when the API must be able to return borrowed data or owned data.

Summary

The std::borrow module connects owned data and borrowed data:

  • Borrow<T> is a contract: the borrowed form and the owned form are interchangeable for hashing and comparison. This contract is the reason that HashMap lookups are easy to write.
  • ToOwned is the conversion from borrowed to owned: it gives you the owned version of borrowed data.
  • Cow<'a, B> is the optimization tool: it holds borrowed data by default and allocates only when a mutation makes it necessary.

Together, they let you write APIs that are flexible and efficient. The APIs accept each ownership form, and they do not allocate until it is necessary.

Code Examples

FileDescription
01_07_borrow_trait.rsThe Borrow trait with HashMap lookups, and the hash/eq contract
01_08_to_owned.rsToOwned for &str, &[T], and &Path, and the round trip with Borrow
01_09_cow_basics.rsCow basics: the borrowed and owned variants, and deferred allocation
01_10_cow_api_design.rsAPIs with Cow: a Config struct and function return types
01_11_cow_mutation.rsto_mut(), into_owned(), and conditional normalization