How do I work with Cow (Clone on Write) in Rust?
Walkthrough
Cow<'a, B> (Clone on Write) is a smart pointer that can hold either a borrowed reference or an owned value. It defers cloning until mutation is actually needed, optimizing performance when modifications are rare.
Key concepts:
- Two variants —
Borrowed(reference) orOwned(owned value) - Lazy cloning — Only clones when mutation is required
- Zero-cost reads — Reading doesn't trigger cloning
- Generic over
ToOwned— Works with any type implementingToOwned
When to use Cow:
- Function parameters that might need modification
- Returning either borrowed or owned strings
- Optimizing when most values don't need cloning
- Message processing where most messages are passed through
- Configuration values that are usually defaults
Common use cases:
Cow<'a, str>— String data that might be borrowed or ownedCow<'a, [T]>— Slice data that might be borrowed or ownedCow<'a, Path>— Path data that might be borrowed or owned
Code Examples
Basic Cow Usage
use std::borrow::Cow;
fn process(input: Cow<'_, str>) -> Cow<'_, str> {
if input.contains("error") {
// Need to modify - this triggers clone
let mut owned = input.into_owned();
owned.push_str(" [fixed]");
Cow::Owned(owned)
} else {
// No modification needed - return borrowed reference
input
}
}
fn main() {
// Borrowed case - no allocation
let borrowed = process(Cow::Borrowed("hello world"));
println!("Borrowed result: {}", borrowed);
// Owned case - triggers allocation
let owned = process(Cow::Borrowed("error: something went wrong"));
println!("Owned result: {}", owned);
}Cow with String Processing
use std::borrow::Cow;
fn escape_html(input: &str) -> Cow<'_, str> {
// Check if escaping is needed
if input.contains('<') || input.contains('>') || input.contains('&') {
// Need to escape - create owned string
let escaped = input
.replace('&', "&")
.replace('<', "<")
.replace('>', ">");
Cow::Owned(escaped)
} else {
// No escaping needed - return borrowed
Cow::Borrowed(input)
}
}
fn main() {
let safe = escape_html("Hello, World!");
println!("Safe: {} (borrowed)", safe);
let unsafe_str = escape_html("<script>alert('xss')</script>");
println!("Escaped: {} (owned)", unsafe_str);
}Cow with Slices
use std::borrow::Cow;
fn filter_positive<'a>(numbers: Cow<'a, [i32]>) -> Cow<'a, [i32]> {
// Check if any negative numbers exist
if numbers.iter().any(|&n| n < 0) {
// Need to filter - create owned vector
let filtered: Vec<i32> = numbers.iter().copied().filter(|&n| n >= 0).collect();
Cow::Owned(filtered)
} else {
// All positive - return borrowed
numbers
}
}
fn main() {
let all_positive = [1, 2, 3, 4, 5];
let result = filter_positive(Cow::Borrowed(&all_positive));
println!("All positive: {:?}", result);
let has_negative = [1, -2, 3, -4, 5];
let result = filter_positive(Cow::Borrowed(&has_negative));
println!("Filtered: {:?}", result);
}Cow for Function Parameters
use std::borrow::Cow;
// Accept both &str and String without forcing allocation
fn greet(name: impl Into<Cow<'_, str>>) -> String {
let name = name.into();
format!("Hello, {}!", name)
}
// More explicit version
fn greet_explicit(name: Cow<'_, str>) -> String {
format!("Hello, {}!", name)
}
fn main() {
// Works with &str
let greeting1 = greet("Alice");
println!("{}", greeting1);
// Works with String
let greeting2 = greet(String::from("Bob"));
println!("{}", greeting2);
// Works with Cow
let greeting3 = greet_explicit(Cow::Borrowed("Charlie"));
println!("{}", greeting3);
}Cow with Path
use std::borrow::Cow;
use std::path::{Path, PathBuf};
fn resolve_path<'a>(base: &Path, relative: Cow<'a, Path>) -> Cow<'a, Path> {
if relative.is_absolute() {
// Already absolute - return as-is
relative
} else {
// Need to join - create owned path
let resolved = base.join(&*relative);
Cow::Owned(resolved)
}
}
fn main() {
let base = Path::new("/home/user");
// Absolute path - returned as borrowed
let absolute = Cow::Borrowed(Path::new("/etc/config"));
let result = resolve_path(base, absolute);
println!("Absolute: {:?}", result);
// Relative path - needs joining
let relative = Cow::Borrowed(Path::new("documents/file.txt"));
let result = resolve_path(base, relative);
println!("Resolved: {:?}", result);
}Cow with to_mut
use std::borrow::Cow;
fn main() {
let mut cow: Cow<'_, str> = Cow::Borrowed("hello");
// to_mut returns &mut to owned data
// Clones automatically if currently borrowed
cow.to_mut().push_str(" world");
println!("Modified: {}", cow);
// Now cow is Owned
match cow {
Cow::Owned(ref s) => println!("Is owned: {}", s),
Cow::Borrowed(s) => println!("Is borrowed: {}", s),
}
}Cow in Structs
use std::borrow::Cow;
struct Config<'a> {
name: Cow<'a, str>,
value: Cow<'a, str>,
}
impl<'a> Config<'a> {
fn new(name: impl Into<Cow<'a, str>>, value: impl Into<Cow<'a, str>>) -> Self {
Self {
name: name.into(),
value: value.into(),
}
}
fn with_defaults() -> Self {
Self {
name: Cow::Borrowed("default_name"),
value: Cow::Borrowed("default_value"),
}
}
}
fn main() {
// Static config - uses borrowed references
let config1 = Config::with_defaults();
println!("Name: {}, Value: {}", config1.name, config1.value);
// Dynamic config - uses owned strings
let name = String::from("custom_name");
let config2 = Config::new(name, "static_value");
println!("Name: {}, Value: {}", config2.name, config2.value);
}Cow for Caching
use std::borrow::Cow;
use std::collections::HashMap;
struct StringCache<'a> {
cache: HashMap<String, Cow<'a, str>>,
}
impl<'a> StringCache<'a> {
fn new() -> Self {
Self {
cache: HashMap::new(),
}
}
fn get_or_compute<F>(&mut self, key: &str, compute: F) -> &str
where
F: FnOnce() -> String,
{
self.cache.entry(key.to_string()).or_insert_with(|| {
let computed = compute();
Cow::Owned(computed)
});
// Return reference to the cached value
// Note: This is a simplified example
self.cache.get(key).map(|cow| cow.as_ref()).unwrap()
}
}
fn main() {
let mut cache = StringCache::new();
let value = cache.get_or_compute("greeting", || {
println!("Computing greeting...");
String::from("Hello, World!")
});
println!("Value: {}", value);
}Cow with Serde
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Message<'a> {
#[serde(borrow)]
text: Cow<'a, str>,
timestamp: u64,
}
fn main() {
// Deserialization can use borrowed data
let json = r"{"text":"Hello","timestamp":12345}";
let msg: Message = serde_json::from_str(json).unwrap();
println!("Message: {:?}", msg);
// Can also create with owned data
let owned_msg = Message {
text: Cow::Owned(String::from("Owned message")),
timestamp: 67890,
};
println!("Owned message: {:?}", owned_msg);
}Cow for Zero-Copy Parsing
use std::borrow::Cow;
struct Header<'a> {
key: Cow<'a, str>,
value: Cow<'a, str>,
}
impl<'a> Header<'a> {
fn parse(input: &'a str) -> Option<Self> {
let (key, value) = input.split_once(':')?;
Some(Self {
key: Cow::Borrowed(key.trim()),
value: Cow::Borrowed(value.trim()),
})
}
}
struct Request<'a> {
headers: Vec<Header<'a>>,
}
impl<'a> Request<'a> {
fn parse(input: &'a str) -> Self {
let headers = input
.lines()
.filter_map(|line| Header::parse(line))
.collect();
Self { headers }
}
}
fn main() {
let raw_request = "Content-Type: application/json\nContent-Length: 42\n";
let request = Request::parse(raw_request);
for header in &request.headers {
println!("{}: {}", header.key, header.value);
}
}Cow with into_owned
use std::borrow::Cow;
fn ensure_owned(input: Cow<'_, str>) -> String {
// into_owned always returns owned data
// Clones if currently borrowed, moves if owned
input.into_owned()
}
fn main() {
let borrowed = Cow::Borrowed("borrowed string");
let owned = ensure_owned(borrowed);
println!("Now owned: {}", owned);
let already_owned = Cow::Owned(String::from("already owned"));
let still_owned = ensure_owned(already_owned);
println!("Still owned: {}", still_owned);
}Cow Performance Comparison
use std::borrow::Cow;
// Without Cow - always allocates
fn process_always_clone(input: &str) -> String {
let mut result = input.to_string(); // Always clones
if result.contains("error") {
result.push_str(" [fixed]");
}
result
}
// With Cow - only allocates when needed
fn process_lazy_clone(input: &str) -> Cow<'_, str> {
if input.contains("error") {
let mut owned = input.to_string();
owned.push_str(" [fixed]");
Cow::Owned(owned)
} else {
Cow::Borrowed(input) // No allocation!
}
}
fn main() {
let inputs = vec![
"hello world",
"foo bar",
"error: something",
"baz qux",
];
// Count allocations
let mut owned_count = 0;
for input in &inputs {
let result = process_lazy_clone(input);
match result {
Cow::Owned(_) => owned_count += 1,
Cow::Borrowed(_) => {},
}
println!("Result: {}", result);
}
println!("Allocations: {}/{}", owned_count, inputs.len());
}Custom Types with Cow
use std::borrow::{Cow, ToOwned};
#[derive(Debug, Clone)]
struct MyData {
values: Vec<i32>,
}
impl ToOwned for MyData {
type Owned = MyData;
fn to_owned(&self) -> Self::Owned {
self.clone()
}
}
fn process_data<'a>(data: Cow<'a, MyData>) -> Cow<'a, MyData> {
if data.values.iter().any(|&v| v < 0) {
let mut owned = data.into_owned();
owned.values.retain(|&v| v >= 0);
Cow::Owned(owned)
} else {
data
}
}
fn main() {
let data = MyData { values: vec![1, 2, 3] };
let result = process_data(Cow::Borrowed(&data));
println!("Result: {:?}", result);
let negative_data = MyData { values: vec![1, -2, 3] };
let result = process_data(Cow::Borrowed(&negative_data));
println!("Filtered: {:?}", result);
}Cow in Async Code
use std::borrow::Cow;
async fn process_message(msg: Cow<'_, str>) -> Cow<'_, str> {
// Simulate async processing
if msg.starts_with("echo:") {
let response = format!("Echo: {}", &msg[5..]);
Cow::Owned(response)
} else {
msg
}
}
#[tokio::main]
async fn main() {
let msg1 = Cow::Borrowed("echo: Hello");
let result1 = process_message(msg1).await;
println!("Result: {}", result1);
let msg2 = Cow::Borrowed("plain message");
let result2 = process_message(msg2).await;
println!("Result: {}", result2);
}Summary
Cow Variants:
| Variant | Description | Cost |
|---|---|---|
Borrowed(&'a B) |
Holds a reference | Zero-cost |
Owned(<B as ToOwned>::Owned) |
Holds owned value | Allocation on creation |
Cow Methods:
| Method | Description |
|---|---|
into_owned() |
Returns owned value, cloning if needed |
to_mut() |
Returns mutable reference, cloning if borrowed |
is_borrowed() |
Returns true if borrowed variant |
is_owned() |
Returns true if owned variant |
as_ref() |
Returns reference to inner value |
Cow Use Cases:
| Scenario | Benefit |
|---|---|
| Function parameters | Accept both borrowed and owned |
| Zero-copy parsing | Defer allocation until needed |
| Message processing | Most messages pass through unchanged |
| Configuration | Default values are static strings |
| HTML escaping | Only allocate when escaping needed |
| Path resolution | Only allocate for relative paths |
Performance Characteristics:
| Operation | Borrowed | Owned |
|---|---|---|
Deref |
Cheap | Cheap |
into_owned() |
Clone | Move |
to_mut() |
Clone | Cheap |
as_ref() |
Cheap | Cheap |
Key Points:
- Cow enables lazy cloning for better performance
- Use
Cow<'a, str>for strings that might be borrowed or owned - Use
Cow<'a, [T]>for slices that might be borrowed or owned to_mut()provides mutable access, cloning if necessaryinto_owned()guarantees ownership, cloning if necessary- Works with any type implementing
ToOwned - Zero-cost for read-only access to borrowed data
- Integrates well with Serde for zero-copy deserialization
- Prefer Cow when most data doesn't need modification
