Loading page…
Rust walkthroughs
Loading page…
Cow<'a, B> smart pointer in std::borrow and when is it useful for avoiding allocations?Cow<'a, B> (Clone-on-Write) is a smart pointer that holds either a borrowed reference or an owned value. It enables you to write APIs that accept borrowed data when possible but can produce owned data when necessary, avoiding allocations when the borrowed form suffices. The name comes from the optimization strategy: you borrow data until you need to modify it, at which point you clone to get an owned copy.
use std::borrow::Cow;
fn describe_cow() {
// Cow can hold either a borrowed reference or owned data
let borrowed: Cow<str> = Cow::Borrowed("hello");
let owned: Cow<str> = Cow::Owned(String::from("hello"));
// Both work the same way through the Cow interface
println!("Borrowed: {}", borrowed);
println!("Owned: {}", owned);
// Cow derefs to the underlying type
let length = borrowed.len(); // Works like &str
}A Cow holds one of two states without requiring allocation until mutation is needed.
use std::borrow::Cow;
fn process(input: &str) -> Cow<str> {
if input.contains("placeholder") {
// Need to modify - return owned String
Cow::Owned(input.replace("placeholder", "value"))
} else {
// No modification needed - return borrowed reference
Cow::Borrowed(input)
}
}
fn example() {
let s1 = "hello world";
let s2 = "hello placeholder world";
let result1 = process(s1);
let result2 = process(s2);
// result1 borrows s1 (no allocation)
// result2 owns new String (allocated)
println!("Result 1: {}", result1);
println!("Result 2: {}", result2);
}When the input doesn't need modification, we avoid allocation entirely.
use std::borrow::Cow;
// Cow<'a, B> where B: ToOwned
// Common types B:
// - str (ToOwned = String)
// - [T] (ToOwned = Vec<T>)
// - Path (ToOwned = PathBuf)
// - CStr (ToOwned = CString)
// - OsStr (ToOwned = OsString)
fn type_examples() {
// Cow<str> - most common for strings
let string_cow: Cow<str> = Cow::Borrowed("hello");
// Cow<[u8]> - for byte slices
let bytes_cow: Cow<[u8]> = Cow::Borrowed(&[1, 2, 3]);
// Cow<Path> - for paths
use std::path::Path;
let path_cow: Cow<Path> = Cow::Borrowed(Path::new("/usr/bin"));
}Cow works with any type that implements ToOwned.
use std::borrow::Cow;
// Without Cow - always allocates
fn add_suffix_allocating(input: &str) -> String {
let mut result = input.to_string(); // Always allocates
result.push_str("_suffix");
result
}
// With Cow - only allocates when needed
fn add_suffix_cow(input: &str) -> Cow<str> {
if input.is_empty() {
// Static string, no allocation
Cow::Borrowed("_suffix")
} else {
// Must allocate to concatenate
Cow::Owned(format!("{}_suffix", input))
}
}
// Better: only allocate when actually modifying
fn transform_if_needed(input: &str) -> Cow<str> {
if input.starts_with("http://") {
// Need to transform - allocate
Cow::Owned(input.replacen("http://", "https://", 1))
} else {
// No change needed - borrow
Cow::Borrowed(input)
}
}
fn comparison() {
let url = "https://example.com";
let http_url = "http://example.com";
// No allocation for already-https URL
let result1 = transform_if_needed(url);
// Allocation for http URL
let result2 = transform_if_needed(http_url);
println!("Result 1: {}", result1);
println!("Result 2: {}", result2);
}The Cow version only allocates when modification is necessary.
use std::borrow::Cow;
// Accept either &str or String without forcing allocation
fn greet(name: impl Into<Cow<'static, str>>) {
let name: Cow<str> = name.into();
println!("Hello, {}!", name);
}
fn greet_flexible<'a>(name: impl Into<Cow<'a, str>>) {
let name: Cow<str> = name.into();
println!("Hello, {}!", name);
}
fn function_parameters() {
// Can pass borrowed
greet("Alice");
// Can pass owned
greet(String::from("Bob"));
// Can pass Cow itself
let cow: Cow<str> = Cow::Borrowed("Charlie");
greet(cow);
}Functions can accept both owned and borrowed data through Cow.
use std::borrow::Cow;
fn clone_on_write() {
let original = "hello";
let mut cow: Cow<str> = Cow::Borrowed(original);
// to_mut() returns &mut to owned data
// If borrowed, clones first
cow.to_mut().push_str(" world");
// Now cow is Owned, original is unchanged
println!("Original: {}", original); // "hello"
println!("Cow: {}", cow); // "hello world"
// to_mut() on already-owned doesn't clone
cow.to_mut().push_str("!");
println!("Cow: {}", cow); // "hello world!"
}
fn clone_only_when_needed() {
let original = String::from("hello");
let mut cow: Cow<str> = Cow::Owned(original);
// Already owned - no clone needed
cow.to_mut().push_str(" world");
// If we had borrowed, this would have cloned
}The to_mut() method clones only when the Cow is currently borrowed.
use std::borrow::Cow;
fn process_bytes(input: &[u8]) -> Cow<[u8]> {
// Check if modification is needed
if input.contains(&b'\r') {
// Need to remove carriage returns - allocate
let cleaned: Vec<u8> = input.iter()
.filter(|&&b| b != b'\r')
.copied()
.collect();
Cow::Owned(cleaned)
} else {
// No modification needed - borrow
Cow::Borrowed(input)
}
}
fn byte_examples() {
let clean_data = b"hello\nworld";
let dirty_data = b"hello\r\nworld";
// No allocation for clean data
let result1 = process_bytes(clean_data);
// Allocation for dirty data
let result2 = process_bytes(dirty_data);
println!("Clean result: {:?}", result1);
println!("Dirty result: {:?}", result2);
}Cow works with byte slices to avoid Vec allocations.
use std::borrow::Cow;
use std::path::{Path, PathBuf};
fn normalize_path(path: &Path) -> Cow<Path> {
let path_str = path.to_string_lossy();
if path_str.contains("./") || path_str.contains("../") {
// Need to resolve - allocate
let mut result = PathBuf::new();
let mut components = Vec::new();
for component in path.components() {
match component.as_os_str().to_str() {
Some(".") => {} // Skip
Some("..") => { components.pop(); }
_ => components.push(component.as_os_str()),
}
}
for c in components {
result.push(c);
}
Cow::Owned(result)
} else {
// Already normalized - borrow
Cow::Borrowed(path)
}
}
fn path_examples() {
let simple = Path::new("/usr/bin");
let relative = Path::new("/usr/local/../bin");
let result1 = normalize_path(simple); // Borrowed
let result2 = normalize_path(relative); // Owned
println!("Simple: {:?}", result1);
println!("Relative: {:?}", result2);
}Path normalization often benefits from Cow's allocation avoidance.
use std::borrow::Cow;
// API that returns Cow allows caller to decide on allocation
fn get_config_value(key: &str) -> Cow<str> {
// Simulated config lookup
match key {
"default_timeout" => Cow::Borrowed("30"), // Static - no allocation
"app_name" => Cow::Borrowed("MyApp"), // Static - no allocation
"computed_path" => {
// Dynamic - must allocate
Cow::Owned(format!("/var/lib/{}/data", "myapp"))
}
_ => Cow::Borrowed(""), // Default - no allocation
}
}
fn return_optimization() {
let timeout = get_config_value("default_timeout");
let path = get_config_value("computed_path");
// timeout is borrowed (static string)
// path is owned (computed at runtime)
println!("Timeout: {}", timeout);
println!("Path: {}", path);
}Functions can return static strings without allocation.
use std::borrow::Cow;
fn into_owned_example() {
let borrowed: Cow<str> = Cow::Borrowed("hello");
let owned: Cow<str> = Cow::Owned(String::from("world"));
// into_owned() always produces owned data
let s1: String = borrowed.into_owned(); // Clones from borrowed
let s2: String = owned.into_owned(); // Takes ownership
println!("s1: {}", s1);
println!("s2: {}", s2);
}
// Useful when you need to guarantee ownership
fn process_and_own(input: Cow<str>) -> String {
// Do some processing
let processed = if input.starts_with("prefix:") {
Cow::Owned(input[7..].to_string())
} else {
input
};
// Ensure we own the result
processed.into_owned()
}into_owned() converts either variant to an owned value.
use std::borrow::Cow;
fn validate_username(username: &str) -> Result<(), Cow<str>> {
if username.is_empty() {
// Static error - no allocation
Err(Cow::Borrowed("Username cannot be empty"))
} else if username.len() < 3 {
// Dynamic error - must allocate
Err(Cow::Owned(format!(
"Username '{}' is too short (minimum 3 characters)",
username
)))
} else if username.contains(char::is_whitespace) {
// Static error - no allocation
Err(Cow::Borrowed("Username cannot contain whitespace"))
} else {
Ok(())
}
}
fn error_examples() {
match validate_username("") {
Ok(()) => println!("Valid"),
Err(e) => println!("Error: {}", e), // No allocation
}
match validate_username("ab") {
Ok(()) => println!("Valid"),
Err(e) => println!("Error: {}", e), // Allocated
}
}Error messages often mix static and dynamic content, making Cow ideal.
use std::borrow::Cow;
fn chain_operations(input: &str) -> Cow<str> {
let result = Cow::Borrowed(input);
// Chain operations - only allocates when needed
let result = trim_if_needed(result);
let result = normalize_whitespace(result);
let result = add_prefix_if_missing(result, "item:");
result
}
fn trim_if_needed(input: Cow<str>) -> Cow<str> {
let trimmed = input.trim();
if trimmed.len() != input.len() {
Cow::Owned(trimmed.to_string())
} else {
input // Already trimmed, no new allocation
}
}
fn normalize_whitespace(input: Cow<str>) -> Cow<str> {
// Only allocate if multiple spaces exist
if input.contains(" ") {
let normalized: String = input.split_whitespace()
.collect::<Vec<_>>()
.join(" ");
Cow::Owned(normalized)
} else {
input
}
}
fn add_prefix_if_missing<'a>(input: Cow<'a, str>, prefix: &'a str) -> Cow<'a, str> {
if input.starts_with(prefix) {
input
} else {
Cow::Owned(format!("{}{}", prefix, input))
}
}
fn chaining_example() {
let input1 = " item:foo "; // Needs trim and has prefix
let input2 = "item:bar"; // No changes needed
let result1 = chain_operations(input1); // Allocations
let result2 = chain_operations(input2); // No allocations
println!("Result 1: {}", result1);
println!("Result 2: {}", result2);
}Operations can chain Cow values, accumulating changes efficiently.
use std::borrow::Cow;
// Option 1: Always allocate
fn always_allocate(input: &str) -> String {
let mut result = input.to_string(); // Always allocates
if result.is_empty() {
result.push_str("default");
}
result
}
// Option 2: Return static or dynamic with enum
#[derive(Debug)]
enum MaybeOwned<'a> {
Static(&'static str),
Borrowed(&'a str),
Owned(String),
}
// More complex to work with
// Option 3: Use Cow
fn with_cow(input: &str) -> Cow<str> {
if input.is_empty() {
Cow::Borrowed("default") // No allocation
} else {
Cow::Borrowed(input) // No allocation
}
}
fn comparison() {
let empty = "";
let value = "test";
// always_allocate creates String for both
let s1 = always_allocate(empty);
let s2 = always_allocate(value);
// with_cow only allocates if needed
let c1 = with_cow(empty);
let c2 = with_cow(value);
// Both c1 and c2 are borrowed in this case
}Cow provides a standard library solution for this common pattern.
use std::borrow::Cow;
fn trait_impls() {
let cow: Cow<str> = Cow::Borrowed("hello");
// Deref to the borrowed type
let len: usize = cow.len(); // &str methods
let is_empty: bool = cow.is_empty();
let chars = cow.chars();
// Clone
let cow2: Cow<str> = Cow::Owned(String::from("world"));
let cloned = cow2.clone(); // Deep clones if Owned
// Debug, Display
println!("Display: {}", cow);
println!("Debug: {:?}", cow);
// From and Into
let from_str: Cow<str> = "static".into();
let from_string: Cow<str> = String::from("owned").into();
let from_ref: Cow<str> = Cow::Borrowed("borrowed");
// AsRef
let as_ref: &str = cow.as_ref();
}
// Generic over anything that can become a Cow
fn accept_cow_input<'a>(input: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
input.into()
}Cow implements standard traits for ergonomic use.
use std::borrow::Cow;
fn performance_comparison() {
// Scenario: Transform 1000 strings, 90% don't need changes
// Without Cow: 1000 allocations
let inputs: Vec<&str> = (0..1000)
.map(|i| if i % 10 == 0 { "needs_change" } else { "ok" })
.collect();
// Approach 1: Always allocate
let allocated: Vec<String> = inputs.iter()
.map(|s| {
if s.starts_with("needs") {
s.to_uppercase()
} else {
s.to_string() // Unnecessary allocation
}
})
.collect();
// Approach 2: Use Cow
let cow_results: Vec<Cow<str>> = inputs.iter()
.map(|s| {
if s.starts_with("needs") {
Cow::Owned(s.to_uppercase())
} else {
Cow::Borrowed(*s) // No allocation
}
})
.collect();
// cow_results has only ~100 allocations instead of 1000
}Cow can significantly reduce allocations when most inputs don't need modification.
use std::borrow::Cow;
// Use Cow when:
// 1. You have a mix of static and dynamic data
// 2. Most inputs don't need modification
// 3. You want to defer or avoid allocations
// 4. API flexibility for callers
// Example 1: Static default with dynamic override
fn get_message(name: Option<&str>) -> Cow<str> {
match name {
Some(n) => Cow::Owned(format!("Hello, {}!", n)),
None => Cow::Borrowed("Hello, World!"),
}
}
// Example 2: Transform only when needed
fn sanitize_html(input: &str) -> Cow<str> {
if input.contains('<') || input.contains('>') {
Cow::Owned(input
.replace('<', "<")
.replace('>', ">"))
} else {
Cow::Borrowed(input)
}
}
// Example 3: Caching with Cow
struct Cache {
default: &'static str,
}
impl Cache {
fn get(&self, key: &str) -> Cow<str> {
if key == "default" {
Cow::Borrowed(self.default)
} else {
// Simulated lookup
Cow::Owned(format!("value_for_{}", key))
}
}
}Cow excels in scenarios with conditional modification.
use std::borrow::Cow;
// Don't use Cow when:
// 1. You always need to modify
// 2. Lifetime management becomes too complex
// 3. The allocation overhead isn't worth it
// Always modifies - Cow doesn't help
fn always_modify(input: &str) -> String {
// Would always be Owned anyway
input.to_uppercase()
}
// Lifetime complexity
fn complex_lifetimes<'a, 'b>(a: &'a str, b: &'b str) -> Cow<'?, str> {
// Lifetime of Cow unclear - which one?
// Cow<'a, str> or Cow<'b, str>?
// Often simpler to just return String
if a.is_empty() {
Cow::Borrowed(b)
} else {
Cow::Borrowed(a)
}
}Cow adds complexity that isn't always worthwhile.
Cow<'a, B> provides clone-on-write semantics that optimize for the common case where data doesn't need modification:
Key concepts:
| Aspect | Description |
|--------|-------------|
| Borrowed variant | Holds &'a B - no allocation |
| Owned variant | Holds <B as ToOwned>::Owned |
| to_mut() | Returns &mut to owned data, clones if borrowed |
| into_owned() | Extracts owned data, clones if borrowed |
When Cow helps:
When to avoid Cow:
Key insight: Cow is most valuable when the borrowed case is common and the owned case is rare, letting you avoid allocations for the majority of inputs while still handling modifications when necessary.