Cow (Copy-on-Write)
Cow: Copy-on-Write Smart Pointer
Cow (Clone-on-Write) is a smart pointer that provides efficient handling of borrowed vs. owned data. It's defined as:
pub enum Cow<'a, B: ?Sized + ToOwned> {
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}The key insight is that Cow allows you to work with data that might be borrowed or owned, and only clones when mutation is actually needed. This is a powerful optimization pattern when:
- You often receive data that doesn't need modification
- Cloning is expensive (large strings, vectors)
- You want a unified API that handles both borrowed and owned data
Key Operations
Cow::Borrowed(data)- Wrap borrowed dataCow::Owned(data)- Wrap owned datacow.to_mut()- Get a mutable reference, cloning if borrowedcow.into_owned()- Convert to owned, cloning if borrowedCow::from(data)- Create from &str, String, &[T], Vec<T>, etc.
When to Use Cow
- Functions returning conditionally modified data: Return borrowed when unchanged, owned when modified
- APIs accepting both borrowed and owned: Single function handles &str and String
- Caching/memoization: Store results that might be borrowed or computed
- Configuration defaults: Use default (borrowed) unless overridden (owned)
Your Task
Implement the following functions demonstrating Cow patterns:
Part 1: Basic Cow Usage
-
maybe_uppercase- Convert to uppercase only if any lowercase letters exist- Returns
Cow::Borrowedif already all uppercase (or empty) - Returns
Cow::Ownedif conversion was needed
- Returns
-
ensure_suffix- Ensure string ends with suffix- Returns
Cow::Borrowedif suffix already present - Returns
Cow::Ownedwith suffix appended otherwise
- Returns
-
trim_and_lowercase- Trim whitespace and convert to lowercase- Returns
Cow::Borrowedonly if string is already trimmed and lowercase - Returns
Cow::Ownedotherwise
- Returns
Part 2: Cow with Collections
-
remove_zeros- Remove all zeros from a slice- Returns
Cow::Borrowedif no zeros exist - Returns
Cow::Ownedwith zeros filtered out
- Returns
-
deduplicate_sorted- Remove consecutive duplicates from sorted slice- Returns
Cow::Borrowedif no duplicates - Returns
Cow::Ownedwith duplicates removed
- Returns
-
clamp_values- Clamp all values to a range [min, max]- Returns
Cow::Borrowedif all values already in range - Returns
Cow::Ownedwith clamped values
- Returns
Part 3: Modifying Cow with to_mut()
-
ensure_capacity- Ensure string has at least N characters, padding with char if needed- Uses
to_mut()to modify only when necessary - Returns the modified/unmodified Cow
- Uses
-
modify_if_needed- Apply a transformation function only if predicate returns true- Generic over the transformation
- Uses
to_mut()for efficient in-place modification when already owned
Part 4: Cow in Data Structures
TextProcessor- A struct demonstrating Cow in a real-world scenario- Stores text as
Cow<'a, str> new(text: &'a str)- Create with borrowed textfrom_owned(text: String)- Create with owned textprocess(&mut self)- Apply processing (trim + normalize whitespace)into_string(self)- Convert to Stringis_borrowed(&self)- Check if currently borrowing
- Stores text as
Examples
use cow_copy_on_write::*;
use std::borrow::Cow;
// Basic string operations
let result = maybe_uppercase("HELLO");
// Already uppercase
assert!(matches!(result, Cow::Borrowed(_)));
let result = maybe_uppercase("Hello");
assert_eq!(result, "HELLO"); // Needed conversion
// Suffix handling
let result = ensure_suffix("file.txt", ".txt");
// Already has suffix
assert!(matches!(result, Cow::Borrowed(_)));
let result = ensure_suffix("file", ".txt");
assert_eq!(result, "file.txt");
// Collection operations
let data = [1, 2, 3, 4];
let result = remove_zeros(&data);
assert!(matches!(result, Cow::Borrowed(_))); // No zeros
let data = [0, 1, 0, 2];
let result = remove_zeros(&data);
assert_eq!(result.as_ref(), &[1, 2]);
// Using to_mut() for efficient modification
let cow: Cow<str> = Cow::Borrowed("hi");
let result = ensure_capacity(cow, 5, '!');
assert_eq!(result, "hi!!!");
// TextProcessor
let mut processor = TextProcessor::new(" hello world ");
assert!(processor.is_borrowed());
processor.process();
// Now owned after processing
assert!(!processor.is_borrowed());
assert_eq!(processor.into_string(), "hello world");Hints
Click here for hints
- Use
matches!(cow, Cow::Borrowed(_))to check if borrowed to_mut()returns&mut B::Ownedand clones if currently borrowedinto_owned()consumes the Cow and returns owned data- For strings:
Cow<'a, str>withCow::Borrowed(&str)andCow::Owned(String) - For slices:
Cow<'a, [T]>withCow::Borrowed(&[T])andCow::Owned(Vec<T>) - Check conditions BEFORE creating Cow to decide borrowed vs owned
- The
Cow::from()trait createsCow::Borrowedfrom references andCow::Ownedfrom owned types - When returning Cow from a function, the lifetime ties to the input reference
Run to execute your code and see test results here.