So you've written a function that takes a large input String. Most of the time, you only need to read from this string—but occasionally, you'll need to modify it or extract an owned version. Cloning the string every time would be wasteful, but managing both owned and borrowed types separately can quickly get messy.
That's where Rust's smart pointer Cow<T> (Clone on Write) comes in. It lets you start with a borrowed reference and only clone into an owned value when mutation is needed.
In this article, we'll walk through how Cow<T> works, how to use it in your structs and functions, the benefits and trade-offs of using it, and a few common pitfalls to avoid—so you can write clean, efficient Rust code without over-engineering your ownership logic.
The Borrow‑vs‑Own Dilemma
Remember our scenario from the first paragraph - what's the simplest signature you'd reach for? Probably:
fn escape_html(html_input: &mut String) -> &String { /* … */ }
While this allows you to scan and mutate the string in place, it has several downsides:
Callers must own a
String
(no&str)
, even if they only ever read.They need a mutable borrow up front, blocking other shared access.
Any mutation can reallocate, invalidating other references.
By contrast, with Cow<str>
you get one signature that's both flexible and lazy:
use std::borrow::Cow;
fn escape_html<'a>(mut input: Cow<'a, str>) -> Cow<'a, str> { /* … */ }
Accepts
&str
,String
, or even anotherCow<'_, str>
.Fast path: if there's nothing to escape, you just return the borrow—no clone, no allocation.
Slow path: you call
.into_owned()
(or.to_mut())
, pay for exactly one clone, then mutate freely.
This can be really useful if you decide to make the entire operation parallelized.
What Is Cow<T>
?
The Cow<T>
is simply a smart pointer, declared as an enum
capable of holding two variants: an owned variant and a borrowed variant.
This allows it to efficiently implement Copy-on-Write
hence its name Cow
. How does it do this, you ask? Well, since it holds two variants, it can hold a reference initially, and only when an owned value is needed would it call the to_owned
function, allowing it to have an owned handle to the inner item.
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "Cow")]
pub enum Cow<'a, B: ?Sized + 'a>
where
B: ToOwned,
{
/// Borrowed data.
#[stable(feature = "rust1", since = "1.0.0")]
Borrowed(#[stable(feature = "rust1", since = "1.0.0")] &'a B),
/// Owned data.
#[stable(feature = "rust1", since = "1.0.0")]
Owned(#[stable(feature = "rust1", since = "1.0.0")] <B as ToOwned>::Owned),
}
The above code is the official actual declaration for the smart pointer Cow<T>
. Ignore the macros
and let's focus on line 5:
This line ensures that the inner type B implements the
ToOwned
trait. This would be useful to later copy this borrowed value if an owned variant is ever needed, like when you want to perform a write operation, hence fulfilling theCoW
.
How to use Cow
Since Cow
is a smart pointer like Rc
or Arc
, it can be used as a type in structs and literally any other place any other types can be used. However, there are some differences in its usage
and declaration
. In this section, we'll explain how you can use it in custom types, and also fn
declarations as well as impl
blocks and common methods
you would need to know.
Basic Usage
Let's look at some examples of how to use Cow<T>
in practice:
use std::borrow::Cow;
// Creating a Cow from a borrowed str
let borrowed_str: Cow<'_, str> = Cow::Borrowed("Hello, world!");
// Creating a Cow from an owned String
let owned_string = String::from("Hello, world!");
let owned_cow: Cow<'_, str> = Cow::Owned(owned_string);
// Automatic conversion using From/Into traits
let borrowed_auto: Cow<'_, str> = "Hello, world!".into();
let owned_auto: Cow<'_, str> = String::from("Hello, world!").into();
One of the most important features of Cow<T>
is the ability to convert from borrowed to owned data only when needed:
use std::borrow::Cow;
fn process_data(mut data: Cow<'_, str>) -> Cow<'_, str> {
// Check if the data needs processing
if data.contains("world") {
// If we need to modify, we first get a mutable reference to an owned value
// This will clone the data if it was borrowed
let mut owned_data = data.to_mut();
// Now we can modify without worry
owned_data.push_str("!!!");
owned_data.replace_range(0..5, "Hi");
// `data` is now owned
data
} else {
// No modification needed, return as is
data
}
}
// This won't clone:
let input = Cow::Borrowed("Hello!");
let result = process_data(input);
assert!(matches!(result, Cow::Borrowed(_)));
// This will clone:
let input = Cow::Borrowed("Hello, world!");
let result = process_data(input);
assert!(matches!(result, Cow::Owned(_)));
assert_eq!(result, "Hi, world!!!!");
Important Methods
Here are the most commonly used methods with Cow<T>
:
use std::borrow::Cow;
// Create a borrowed Cow
let mut cow: Cow<'_, str> = Cow::Borrowed("Hello");
// Check which variant we have
if cow.is_borrowed() {
println!("We have a borrowed value");
}
// Using the matches! macro (available since Rust 1.42.0)
if matches!(cow, Cow::Borrowed(_)) {
println!("Also checking for borrowed variant");
}
// Converting to an owned value
// This clones only if necessary
let owned_string: String = cow.into_owned();
// Getting a mutable reference to the owned data
// This will clone only if it's currently borrowed
let mutable_ref = cow.to_mut();
mutable_ref.push_str(", world!");
// After calling to_mut(), cow is now definitely Owned
assert!(matches!(cow, Cow::Owned(_)));
assert_eq!(cow, "Hello, world!");
Using Cow in a Custom Structure or Enum
When you're defining your own types that use Cow<T>, you need to include proper lifetime annotations:
use std::borrow::Cow;
// A simple struct using Cow
struct Document<'a> {
title: Cow<'a, str>,
content: Cow<'a, str>,
tags: Vec<Cow<'a, str>>,
}
// An enum using Cow
enum Message<'a> {
Text(Cow<'a, str>),
Binary(Cow<'a, [u8]>),
}
// Create a document with mixed borrowed and owned data
fn create_document<'a>(title: &'a str, content: String) -> Document<'a> {
Document {
title: Cow::Borrowed(title), // No allocation
content: Cow::Owned(content), // Already owned
tags: vec![
"rust".into(), // Borrowed
String::from("programming").into(), // Owned
],
}
}
When working with Cow<T>
in structs, you typically need to parameterize your struct with a lifetime that matches the potential borrowed data:
use std::borrow::Cow;
// A structure that can hold either borrowed or owned XML data
struct XmlDocument<'a> {
// For ?Sized types like str or [T]
root_element: Cow<'a, str>,
// For sized types
attributes: Vec<(Cow<'a, str>, Cow<'a, str>)>,
}
impl<'a> XmlDocument<'a> {
// Methods can also use Cow effectively
fn with_root<T>(root: T) -> Self
where
T: Into<Cow<'a, str>>
{
XmlDocument {
root_element: root.into(),
attributes: Vec::new(),
}
}
fn add_attribute<K, V>(&mut self, key: K, value: V)
where
K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.attributes.push((key.into(), value.into()));
}
}
This approach gives you maximum flexibility without sacrificing performance.
Passing Cow in Fn
and impl
Block Signatures
When designing functions that use Cow<T>
, there are a few patterns to consider:
use std::borrow::Cow;
// Taking Cow as an input parameter
fn process_name<'a>(name: Cow<'a, str>) -> Cow<'a, str> {
if name.is_empty() {
Cow::Borrowed("Anonymous")
} else {
name
}
}
// Using generic parameters with Into<Cow>
fn greet<'a, T>(name: T) -> String
where
T: Into<Cow<'a, str>>,
{
format!("Hello, {}!", name.into())
}
// Using Cow in impl blocks
impl<'a> Document<'a> {
// Creating a new document with mixed owned/borrowed data
fn new<T>(title: T) -> Self
where
T: Into<Cow<'a, str>>,
{
Document {
title: title.into(),
content: Cow::Borrowed(""),
tags: Vec::new(),
}
}
// Adding content that might be either borrowed or owned
fn with_content<T>(mut self, content: T) -> Self
where
T: Into<Cow<'a, str>>,
{
self.content = content.into();
self
}
}
When designing APIs, consider whether taking parameters as Into<Cow<'_, T>>
is more ergonomic than directly taking Cow<'_, T>
. The former allows callers to pass both owned and borrowed values without explicitly wrapping them in Cow
.
Benefits and Trade-Offs
Benefits
Performance: Eliminates unnecessary allocations when you only need to read data.
Flexibility: Provides a unified API that works with both owned and borrowed data.
Ergonomics: Simplifies function signatures and reduces the need for separate implementations.
Lazy Allocation: Only pays the cost of cloning when actually needed.
Trade-Offs
Complexity: Adds an extra layer of abstraction that can be confusing for newcomers.
Runtime Check: Each call to
.to_mut()
requires a small runtime check to determine if cloning is needed.Surprise Allocations: Might cause unexpected allocations if misused (e.g., if every operation ends up writing).
API Surface: More complex than just using
&T
orT
directly.
Cow<T>
is particularly valuable in:
Text processing libraries
Parsers and serializers
APIs where input might be modified only conditionally
Functions that return either static or dynamic data
It's less useful when:
You always know you need an owned value
You never modify the input
Performance is absolutely critical and even tiny overheads matter
Common Pitfalls
Calling into_owned()
Too Early
use std::borrow::Cow;
fn process_text(text: Cow<'_, str>) -> String {
// BAD: We've thrown away the benefit of Cow by immediately cloning
let owned = text.into_owned();
// All further operations now work on an owned String,
// even if no modification was needed
owned + "!"
}
// BETTER:
fn process_text_better<'a>(text: Cow<'a, str>) -> Cow<'a, str> {
// Only clone if we need to modify
if text.contains("modify me") {
let mut owned = text.into_owned();
owned.push_str("!");
Cow::Owned(owned)
} else {
// Return the original borrowed reference if no change needed
text
}
}
Lifetime Mismatches
use std::borrow::Cow;
struct Parser<'a> {
// This field can only live as long as the borrowed data
current_token: Cow<'a, str>,
}
impl<'a> Parser<'a> {
// This won't work - we can't extend the lifetime
fn parse_and_store(&mut self, input: &'a str) -> Cow<'static, str> {
let parsed = self.parse(input);
// Error: Cannot convert Cow<'a, str> to Cow<'static, str>
parsed
}
// This works because we explicitly clone to 'static
fn parse_and_store_fixed(&mut self, input: &'a str) -> Cow<'static, str> {
let parsed = self.parse(input);
// Explicitly clone to get a 'static lifetime
Cow::Owned(parsed.into_owned())
}
fn parse(&mut self, input: &'a str) -> Cow<'a, str> {
// Parsing logic...
Cow::Borrowed(input)
}
}
Over-engineering Simple Cases
use std::borrow::Cow;
// OVER-ENGINEERED: We always modify the input
fn append_suffix<'a>(s: Cow<'a, str>) -> Cow<'a, str> {
let mut owned = s.into_owned();
owned.push_str("_suffix");
Cow::Owned(owned)
}
// SIMPLER: Just be explicit about taking ownership
fn append_suffix_simpler(mut s: String) -> String {
s.push_str("_suffix");
s
}
// OVER-ENGINEERED: We never modify the input
fn get_length<'a>(s: Cow<'a, str>) -> (Cow<'a, str>, usize) {
let len = s.len();
(s, len)
}
// SIMPLER: Just use a reference
fn get_length_simpler(s: &str) -> (&str, usize) {
(s, s.len())
}
Remember to use Cow<T>
when it genuinely adds value to your API, not just because it seems fancy.
Summary
Cow<T>
is a powerful tool in Rust's ownership system that helps minimize unnecessary allocations. Use Cow<T>
when:
You need to handle both owned and borrowed data with a single API
You only occasionally need to modify or take ownership of data
You want to defer allocation costs until absolutely necessary
Your functions might return either static or dynamic data
Remember that like any abstraction, Cow<T>
comes with trade-offs. It's not always the right choice, but in the right circumstances, it can significantly improve both API ergonomics and performance.
For more information, check out the official documentation for std::borrow::Cow
, also you can look directly into the borrow.rs
file to see how some of these methods are implemented to better understand how Cow
works under the hood.
For more content , make sure to checkout rust daily for more content on using rust.