When one owner isn't enough
You're building a text editor. You have a buffer of characters. One module wants to search for typos. Another wants to insert a newline. You pass the buffer to the search function. The compiler rejects you. You need to decide whether to duplicate the entire buffer or just hand over a reference. This is the clone vs borrow choice. Make the choice based on ownership, not habit.
Borrowing vs cloning in plain words
Borrowing is handing someone a key to your house. They can enter, look around, and even rearrange the furniture if you give them the master key. But they can't build a second house on your lot. Cloning is hiring a crew to construct an identical house next door. You now own two separate houses. If you repaint the kitchen in one, the other stays the same. Building that second house costs time and materials. Borrowing is free. Cloning has a price.
In Rust, borrowing creates a reference. The reference points to the original data. No new memory is allocated for the data itself. The borrow checker tracks how long the reference lives and ensures it never outlives the data. Cloning creates a new, independent copy. Rust allocates fresh memory and duplicates the data. The two values are now unrelated. Dropping one has no effect on the other.
Borrowing is free. Cloning has a price.
Minimal example
fn main() {
// Create a String on the heap.
let original = String::from("Rust is fun");
// Clone creates a full independent copy.
// Memory is allocated again. Data is copied byte-for-byte.
let cloned = original.clone();
// Borrow creates a reference. No new allocation.
// Just a pointer and a promise to the compiler.
let borrowed = &original;
println!("Original: {}", original);
println!("Cloned: {}", cloned);
println!("Borrowed: {}", borrowed);
}
Clone for independence. Borrow for efficiency.
What happens under the hood
When you call .clone(), Rust invokes the Clone trait implementation. For a String, this means allocating a new chunk of heap memory and copying every byte from the original. The two values are now completely independent. Dropping one does not affect the other. When you take a reference with &, Rust creates a pointer on the stack. No heap allocation occurs. The borrow checker records that this reference exists and enforces rules to ensure the reference never outlives the data it points to.
Not everything needs cloning. Types that implement the Copy trait, like integers, floats, and booleans, are duplicated automatically when assigned. The compiler generates the copy instructions for free. You only see .clone() on types that own heap data or have complex internal state, like String, Vec, and Box.
Know what you're cloning. Deep copies cost memory. Shallow copies share it.
Realistic example
/// Represents a user in the system.
struct User {
name: String,
id: u32,
}
/// Greets the user by name.
/// Takes a reference to avoid copying the String.
fn greet(user: &User) {
println!("Hello, {}!", user.name);
}
/// Creates a backup of the user data.
/// Returns a new User struct with cloned fields.
fn backup_user(user: &User) -> User {
User {
// Clone the name to own it in the new struct.
name: user.name.clone(),
// Copy the id (Copy trait, no clone needed).
id: user.id,
}
}
fn main() {
let alice = User {
name: String::from("Alice"),
id: 42,
};
// Borrow alice for greeting. No copy.
greet(&alice);
// Clone alice for backup. New independent struct.
let alice_backup = backup_user(&alice);
// Modify original. Backup is unaffected.
alice.name = String::from("Alice Smith");
println!("Backup name: {}", alice_backup.name);
}
Clone when you need a backup. Borrow when you just need to look.
Pitfalls and compiler errors
If you borrow data that gets dropped, the compiler stops you. E0515 (returned value contains a reference to a temporary value) appears when you try to return a reference to something created inside the function. The data dies when the function returns, so the reference would point to garbage. You must clone the data to return it, or restructure the code to keep the data alive longer.
If you try to use a value after passing it by value to a function, you get E0382 (use of moved value). The function took ownership. You need to borrow the value when passing it, or clone it before passing.
Mutable borrowing has stricter rules. You can have only one mutable reference, and no immutable references, at any time. If you try to create a mutable borrow while an immutable borrow exists, the compiler emits E0502 (cannot borrow as mutable because it is also borrowed as immutable). You must drop the immutable borrows first, or clone the data to create a separate mutable copy.
If the data dies, the reference dies. Clone to keep it alive.
Clone on write
Sometimes you write a function that might modify data, but often won't. Cloning upfront wastes resources when no modification happens. Borrowing fails when modification is needed. Cow (Clone on Write) solves this. Cow holds either a borrowed reference or an owned clone. It starts as a borrow. When you try to mutate, it clones itself internally. This is called clone on write.
use std::borrow::Cow;
/// Processes text, potentially modifying it.
/// Returns Cow to avoid cloning when no change is needed.
fn process_text(input: &str) -> Cow<str> {
if input.contains("bug") {
// Clone only when we need to modify.
Cow::Owned(input.replace("bug", "feature"))
} else {
// Return borrowed data when no modification occurs.
Cow::Borrowed(input)
}
}
Start with a borrow. Clone only when you write.
Function signatures and conventions
Function signatures dictate the contract. If you write fn process(s: String), the caller must pass an owned String. If the caller has a String, they can move it, or they must clone it. If the caller has a &str, they must clone it to create a String. This forces cloning on the caller. If you write fn process(s: &str), the caller can pass a &str, a &String, or even a string literal. No cloning is required. The function borrows the data. This is the preferred signature for most functions. Accept the most general type that satisfies your needs. &str is more general than String. Accept &str unless you need to store the string inside the function. This pattern applies to vectors too. Accept &[T] instead of &Vec<T> or Vec<T>. Slices are more flexible and avoid unnecessary coupling to the vector type.
When working with Rc<T>, calling .clone() does not copy the data. It increments a reference counter. This is cheap. The community convention is to write Rc::clone(&rc) instead of rc.clone(). This makes it obvious to readers that you are cloning the smart pointer, not the underlying data. It prevents confusion where a reader expects a deep copy but gets a shallow reference bump.
Accept the most general type. Let the caller decide.
Cloning is not a sin
New Rust developers often treat .clone() like a compiler error. They see it and panic, thinking they've done something wrong. This is a mistake. Cloning is a tool. It has a cost, but it also provides independence and simplicity. If you are building a configuration struct and you want to create a default instance that the user can modify without affecting the global default, cloning is the right choice. If you are passing a small struct to a thread, cloning is safer than wrestling with lifetimes. Use cloning when it makes the code correct and readable. Avoid cloning only when profiling proves it hurts performance. Premature optimization of cloning leads to code that is hard to read and hard to maintain. Trust the borrow checker to catch lifetime issues, but trust your judgment to use cloning for clarity.
The borrow checker can sometimes be too strict for complex logic. You might have a loop where you need to read and write to the same collection, or a struct where a field references another field. The compiler rejects this. Cloning can break the cycle. Create a clone of the data you need to read, then mutate the original. This satisfies the borrow checker at the cost of memory. This is a valid trade-off. Correctness comes first. Performance comes second.
Clone for clarity. Optimize only when the profiler screams.
When to use clone vs borrow
Use borrowing when you only need to read the data and the original owner must stay alive for the duration of the operation. Use borrowing when you need to modify the original data and the caller expects those changes to persist. Use cloning when the function must own the data because the original value might be dropped before the function finishes its work. Use cloning when you need an independent copy that can be modified without affecting the original source. Use cloning when storing data in a collection that requires owned values, such as inserting a String into a Vec<String>. Reach for Copy types when working with primitives like integers or booleans; the compiler duplicates these automatically without any explicit cloning overhead.
Match the tool to the ownership requirement. Borrow for access. Clone for ownership.