Table of contents
Cow<'a, B> (Copy-on-Write) is a smart pointer in Rust’s std::borrow module that provides a clone-free abstraction over borrowed and owned data. It enables efficient handling of data that may or may not need modification, minimizing allocations while maintaining flexibility.
What is Cow?
Cow (short for Copy-on-Write) can represent:
- Borrowed data (
&'a B): A reference to existing data, avoiding allocations. - Owned data (
<B as ToOwned>::Owned): A fully owned copy, allocated only when mutation is required.
Definition (from std::borrow):
pub enum Cow<'a, B>
where
B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B), // Immutable reference (no allocation)
Owned(<B as ToOwned>::Owned), // Owned data (allocated when needed)
}
How It Works:
- Initially wraps a reference (
Borrowed), which is zero-cost. - Converts to owned data (
Owned) lazily, only when modification is needed.
Example with Cow (Strings)
use std::borrow::Cow;
fn process(input: &str) -> Cow<str> {
if input.contains("error") {
Cow::Owned(input.replace("error", "")) // Allocates new String
} else {
Cow::Borrowed(input) // No allocation
}
}
fn main() {
let msg1 = "hello world"; // No allocation
let msg2 = "error: foo"; // Will allocate when processed
println!("{}", process(msg1)); // "hello world" (borrowed)
println!("{}", process(msg2)); // ": foo" (owned)
}
Key Use Cases
1. Optimizing String Operations
Avoid allocations when modifying strings conditionally:
fn to_uppercase(input: &str) -> Cow<str> {
if input.chars().any(|c| c.is_lowercase()) {
Cow::Owned(input.to_uppercase()) // Allocates only if needed
} else {
Cow::Borrowed(input)
}
}
Extended Example (checking for digits):
fn to_uppercase_no_digits(input: &str) -> Cow<str> {
if input.chars().any(|c| c.is_lowercase() || c.is_digit(10)) {
Cow::Owned(input.to_uppercase().replace(|c: char| c.is_digit(10), ""))
} else {
Cow::Borrowed(input)
}
}
Cow ensures no allocation if the input is already uppercase and digit-free, optimizing read-only paths.
2. API Flexibility
Accept both borrowed and owned data without forcing clones:
fn print(data: Cow<str>) {
println!("{}", data);
}
fn main() {
let my_string = String::from("world");
print(Cow::Borrowed("hello")); // No allocation
print(Cow::Owned(my_string)); // Works too
}
This supports &str, String, or other types implementing ToOwned.
3. Zero-Copy Parsing
Common in parsers (e.g., serde), where fields are often unmodified:
struct JsonValue<'a> {
data: Cow<'a, str>, // Borrows from input unless modified
}
When to Avoid Cow
- Always-mutated data: Use
StringorVecdirectly to avoidCowoverhead. - Thread-safety:
Cowis not thread-safe; useArc+Mutexfor concurrent access.
Performance Implications
| Scenario | Behavior | Allocation Cost |
|---|---|---|
| No modification | Stays as Borrowed |
Zero |
| Modification | Converts to Owned |
One allocation |
Key Takeaways
✅ Use Cow when:
- You need to conditionally modify borrowed data.
- You want to avoid allocations for read-only paths.
- Your API should accept both
&strandStringefficiently.
🚀 Real-world uses:
regex::Match(borrows input strings).serdedeserialization.- Path manipulation (
PathBufvs.&Path).
Note: Cow works with any ToOwned type (e.g., [u8] → Vec<u8], Path → PathBuf).
Experiment: Modifying the to_uppercase example to handle digits (as shown above) demonstrates how Cow avoids allocations unless both lowercase letters and digits are present, optimizing performance.