8.7Clone: explicit deep copies

Last updated June 12, 2026

Twice now the compiler has ended an E0382 with the same offer: "consider cloning the value if the performance cost is acceptable." This lesson takes the offer seriously.

The honest copy

Sometimes you really do want two of a value. Not a handoff, not a loan: two independent Strings, separate lives, separate fates. That's lesson 8.4's "option two" (copy the heap block as well), and Rust does provide it; it just refuses to let = do it silently. The operation has a name you must write:

fn main() {
    let original = String::from("hello");
    let duplicate = original.clone();
    println!("{original} {duplicate}");
}
hello hello

.clone() performs the deep copy: it asks the allocator for a new heap block, copies the text into it, and builds duplicate a handle of its own. Redraw lesson 8.2's diagram with two handles and two blocks, one each. Nothing is shared, so rule 2 (one owner at a time) is satisfied twice over: each block has its one owner, each owner drops its own block at the brace, and original remains alive and usable because nothing moved. The use-after-move program that opened lesson 8.4 becomes legal with eight typed characters, exactly as the compiler's diff suggested.

If it's expensive, Rust makes you type it

Why a method call instead of just letting = do this? Because the cost is real: an allocator request plus copying every byte of the text, however many megabytes that turns out to be. Rust's position, which you've now seen from both sides, is a kind of pricing honesty:

You can audit a Rust function's memory behavior by reading it. Every costly duplication announces itself with the same eight characters; if you don't see .clone(), text isn't being copied.

Author's note

This is my favorite "Rust deleted the problem" example in the course. C++ defaults the other way: assignment deep-copies silently, so innocent-looking code copies vectors and strings invisibly, and learncpp needs most of two chapters (14 and 22) to teach you to spot it, suppress it, and write the machinery that avoids it. Rust made the cheap thing the default and the expensive thing a visible word, and two chapters of another course's homework evaporate.

Clone and Copy, side by side

The chapter's two duplication mechanisms, now that you've met both:

Copy (lesson 8.5)Clone
Triggered byPlain =, automaticYou write .clone()
CostAlways trivial (copies the boxes)Whatever the data weighs
Available onWhole-in-the-box types onlyNearly everything, String included
In the diagramDuplicates the boxDuplicates box and heap block

(For completeness: Copy types have .clone() too, and on them it's just the same trivial box-copy. You'll never need to write five.clone(), but seeing it won't mean anything deep is happening.)

The right uses, and the crutch

Clone is correct when two independent values is the actual requirement. You want to keep an original and mutate a variant; a value must go two places with two futures. Those clones aren't waste, they're the program's logic.

But there's a beginner pattern worth warning about by name, because every Rust learner passes through it. You write code, E0382 appears, the compiler mentions .clone(), you type .clone(), the error disappears. Repeat until the program is confetti'd with clones that exist only to make errors go away. Each one is a real allocation and a real copy that your program's meaning never asked for. The give-and-take-back dance from lesson 8.6 had the same flavor: a workaround for "I just want the function to look at my value," and you've known the real answer's name since that lesson. Lend it. Chapter 9 is one page-turn away, and most error-silencing clones are exactly the clones references make unnecessary.

Best practice

Clone deliberately, not reflexively. Before typing .clone(), ask: does this program genuinely need two independent copies, or do I just need to look at the value from somewhere else? If it's the second, the answer is chapter 9's references, and the clone would be paying an allocation to avoid a lesson.

To close the loop, here's 8.4's forbidden program made legal both ways, with the difference spelled out:

fn main() {
    // Way one: clone, because we genuinely want s1 alive afterwards
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{s1} {s2}");

    // Way two: restructure, because we only needed s1 *before* the move
    let s3 = String::from("world");
    println!("{s3}");
    let s4 = s3;
    println!("{s4}");
}
hello hello
world
world

Way one costs a heap block and buys two independent strings. Way two costs nothing: using the value before moving it often dissolves the conflict without any copy at all. Reordering beats cloning when reordering is possible; cloning is for when it isn't.

Quiz time

Question #1

What does this print, and how many heap blocks were allocated for text over the program's lifetime?

fn main() {
    let a = String::from("ping");
    let b = a.clone();
    let c = b.clone();
    println!("{a} {b} {c}");
}
Show solution
ping ping ping

Three blocks: one from String::from, one per clone. Three owners, three blocks, three drops at the brace, all independent. (If this were chapter-9 code that merely needed to read a three times, zero clones would be required, which is the point of the best-practice box.)

Question #2

Clone, or restructure? For each, say which fix you'd use and why:

a)

fn main() {
    let template = String::from("Dear ____,");
    let letter = template;
    println!("template was: {template}");
}

b)

fn main() {
    let base = String::from("v1");
    let mut experiment = base;
    experiment.push_str("-test");
    println!("{base} -> {experiment}");
}
Show solution

a) Restructure: print before the move (println! first, then let letter = template;). The program only needed template early; no second copy is required, so a clone would be pure waste.

b) Clone: let mut experiment = base.clone();. The program's actual logic wants two strings with different futures (the untouched base and the mutated experiment), so the deep copy is the meaning, not a workaround.

Question #3

A teammate's code contains process(data.clone()) where process's signature is fn process(text: String) -> usize and its body only calls text.len(). Critique the line: who is at fault, the call site or the signature, and what should each ideally be?

Show solution

The signature is the real culprit: a function that only reads the text has no business taking ownership, so process should take &str (lesson 8.6's rule, and exactly what the compiler's "consider changing this parameter type to borrow instead" note keeps suggesting). The call site's .clone() is the symptom: a full copy of the text, allocated and duplicated, purely to feed a function that will read and drop it. Fix the signature and the clone disappears; until then, the give-it-back tuple would also work and would at least not copy. (Chapter 9 makes the good version one & long.)

That's all seven tools of the chapter. The summary collects them, the quiz makes you use them all at once, and then chapter 9 keeps a very large promise.