Cow: Clone-on-Write
Cow (Clone on Write) is a smart enum that holds either borrowed or owned data, letting a function work with a reference for free and allocate a new owned value only at the moment it actually needs to modify it.
Learn Cow: Clone-on-Write in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll use std::borrow::Cow to write functions that avoid needless allocation, return Cow<str> , convert with into_owned , and measure exactly when an allocation happens.
What You'll Learn in This Lesson
1️⃣ Borrowed or Owned, On Demand
Cow<str> is an enum with two variants: Cow::Borrowed(&str) wraps a reference with no allocation, and Cow::Owned(String) wraps a freshly allocated value. A function returns Borrowed on the common "nothing changed" path and Owned only when it must produce new data.
Because Cow derefs to str , you can print it and call string methods on it directly — callers usually don't even need to know which variant they got.
2️⃣ Allocating Only When You Must
The payoff is in functions that transform text. If the input already meets the requirement, return it borrowed — zero cost. If it needs changing, do the work and return it owned. When you finally need a plain owned value, call into_owned() , which clones only if the Cow was still borrowed.
Here "already_clean" needed no change, so its into_owned() performed a single clone, while "needs cleaning up" was already owned from the replace , so into_owned() was free.
3️⃣ Proving the Allocation Savings
To make the benefit concrete, we can count how many times the Owned variant appears across a batch of inputs. Every unchanged message stays Borrowed and costs nothing; only the messages that actually needed editing allocate.
Out of three messages, only one contained the banned word, so exactly one allocation happened. With a plain String return type, all three would have allocated — Cow saved two-thirds of the work.
Your turn. Fill in the blanks marked ___ , then run it.
Write a function that prepends a prefix only when it is missing, borrowing the input otherwise. Run it with cargo run and check the output.
📋 Quick Reference — Cow
Practice quiz
What does Cow stand for?
- Copy on Write
- Container of Wrappers
- Clone on Write
- Closure over Writes
Answer: Clone on Write. Cow is Clone on Write: it borrows for free and only allocates a clone at the moment it must modify the data.
Which two variants does the Cow enum have?
- Borrowed and Owned
- Some and None
- Ok and Err
- Read and Write
Answer: Borrowed and Owned. Cow::Borrowed holds a reference (no allocation); Cow::Owned holds a freshly allocated value.
Which module is Cow imported from?
- std::rc
- std::cell
- std::clone
- std::borrow
Answer: std::borrow. You write use std::borrow::Cow; to bring it into scope.
On the common path where data is unchanged, which variant should a function return?
- Cow::Owned
- Cow::Borrowed
- Cow::Clone
- Cow::None
Answer: Cow::Borrowed. Return Cow::Borrowed when nothing changed so you pay no allocation cost.
What does into_owned() do?
- Always gives a fully owned value, cloning only if it was borrowed
- Always borrows the data
- Frees the value
- Converts Owned back to Borrowed
Answer: Always gives a fully owned value, cloning only if it was borrowed. into_owned() yields an owned value like String; it clones only when the Cow was still Borrowed.
What does Cow::Borrowed take as its argument?
- An owned String
- A Vec
- A reference such as &str
- A closure
Answer: A reference such as &str. Borrowed wraps a reference (like &str); the Owned variant takes the owned String.
Why return Cow<str> instead of always returning String?
- It is faster to type
- It avoids allocating on the unchanged path
- It prevents borrowing
- Strings can't be returned
Answer: It avoids allocating on the unchanged path. Always returning String allocates even when nothing changed; Cow borrows for free on the no-change path.
In a censor function over 3 messages where only 1 contains the banned word, how many allocations (Owned variants) happen?
- 0
- 2
- 3
- 1
Answer: 1. Only the message that actually needed editing allocates, so exactly one Owned variant is produced.
What does Cow's to_mut() do?
- Returns an immutable reference
- Gives a &mut to the data, cloning to owned first if needed
- Deletes the value
- Converts to a slice
Answer: Gives a &mut to the data, cloning to owned first if needed. to_mut() returns a mutable reference, cloning the borrowed data into an owned value first if necessary.
Roughly how much runtime overhead does Cow add on the common unchanged path?
- A full heap allocation every time
- It always doubles memory use
- Very little — it avoids allocation on the common path
- It requires a background thread
Answer: Very little — it avoids allocation on the common path. Cow is a small enum; on the unchanged path it skips heap allocation entirely, so it is typically a win.