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&Thashes and compares the same as the original value.ToOwnedmakes the related owned value from a borrowed value.Cow<'a, B>contains borrowed data or owned data. The program selects the variant at runtime, andCowallocates 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>, thent.borrow()must give a value that hashes identically and compares equally tot.
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 type | to_owned() returns |
|---|---|
str | String |
[T] | Vec<T> |
Path | PathBuf |
OsStr | OsString |
CStr | CString |
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 theCowis borrowed, the method clones the data into the owned variant. Then it returns a mutable reference. If theCowis 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 theCow. If theCowis borrowed, the method clones the data. If theCowis 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
| Situation | Use |
|---|---|
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 unchanged | Cow<T> (deferred allocation) |
An API that accepts &str and String | impl Into<Cow<str>>, or the simpler impl AsRef<str> if you only read the data |
| A processing pipeline with conditional mutation | Cow<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 thatHashMaplookups are easy to write.ToOwnedis 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
| File | Description |
|---|---|
01_07_borrow_trait.rs | The Borrow trait with HashMap lookups, and the hash/eq contract |
01_08_to_owned.rs | ToOwned for &str, &[T], and &Path, and the round trip with Borrow |
01_09_cow_basics.rs | Cow basics: the borrowed and owned variants, and deferred allocation |
01_10_cow_api_design.rs | APIs with Cow: a Config struct and function return types |
01_11_cow_mutation.rs | to_mut(), into_owned(), and conditional normalization |