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
String
orVec
directly to avoidCow
overhead. - Thread-safety:
Cow
is not thread-safe; useArc
+Mutex
for 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
&str
andString
efficiently.
🚀 Real-world uses:
regex::Match
(borrows input strings).serde
deserialization.- Path manipulation (
PathBuf
vs.&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.