8.5Copy types: when assignment copies
A fair objection has been building for two lessons. If assignment moves, how did seven chapters of code like this ever compile?
fn main() {
let x = 5;
let y = x;
println!("{x} {y}");
}5 5
That's x used after let y = x;, the exact shape lesson 8.4 just spent a page forbidding. No E0382, no drama, two usable variables. The compiler even told you why, in the error message you read line by line last lesson: s1 moved because String "does not implement the Copy trait." Implication: some types do, and for them the story is different.
Whole in the box
Think about what made copying a String on = unacceptable. Copying the handle made two owners of one heap block (double free); copying the block made assignment secretly expensive. Both problems come from the same place: a String is more than its box. The handle in the frame is just the visible part; the real cargo lives elsewhere, on the heap.
An i32 has no elsewhere. The value 5 sits whole in its variable's box (lesson 1.3: a name for a box in memory), four bytes, nothing attached. Copy the box and you have copied everything: a complete, independent, honest duplicate, at the cost of moving four bytes. Neither of the move-motivating problems can even be stated. Two boxes holding 5 share nothing, so no double free; and copying four bytes is as cheap as operations get, so no hidden expense.
For types like that, forcing the move rules would be all pain and no protection. So Rust marks them Copy, and for Copy types, = does the obvious thing chapters 1 through 7 always assumed: it copies, and both variables remain alive and independent.
(That word "trait" in the compiler's message is chapter 16's machinery for giving types capabilities; you don't need it yet. Read "implements Copy" as "carries the Copy marker.")
The roster
Every type this course has taught, sorted:
| Type | On let b = a; |
|---|---|
Integers: i32, u64, usize, all of chapter 4's roster | Copies |
Floats: f64, f32 | Copies |
bool | Copies |
char | Copies |
The unit type () | Copies (there's nothing to copy) |
Tuples, if every element is Copy: (i32, f64), (bool, char, u8) | Copies |
Tuples with any non-Copy element: (i32, String) | Moves |
&str | Copies |
String | Moves |
Two rows deserve a closer look.
Tuples are Copy exactly when all their parts are, which follows from the logic above: a tuple of Copy types lives whole in its boxes, so copying the boxes copies everything. Smuggle a String into one element, and the tuple inherits the String's situation: part of its cargo is on the heap, so the whole tuple moves.
&str might look like the surprise, but lesson 5.4 did the work already. A &str is a view: it knows where some text starts and how long it is, and it owns nothing. Copying a view copies the where-and-how-long, not the text, like a second neighbor turning to watch the same bicycle through the same window. No ownership is duplicated because none was there. Views copy freely; owners move.
The one-question test
Does the type own heap memory (or any resource beyond its own bytes)? If no, it can be Copy, and the types you've met all are. If yes, it moves. You now know the deep reason String was introduced as Rust's owned text type: "owned" was this chapter's whole story, compressed into one adjective back in lesson 5.3.
The first half of an old asterisk
Lesson 2.3 flagged its own simplification when it said arguments are copied into parameters: "the whole truth for integers and the other simple types in these chapters, and a simplification for types like String."
You can now cash half of that asterisk. For Copy types, "arguments are copied" was never a simplification at all; the argument's box is copied into the parameter's box in the called function's frame (lesson 8.2), and both stay alive. That's the whole truth, no asterisk. What happens when the argument is a String is the other half, and it's next lesson's headline, though after 8.4 you can probably hear it coming.
Quiz time
Question #1
Compile or not? One sentence of why.
fn main() {
let flag = true;
let copy = flag;
println!("{flag} {copy}");
}Show solution
Compiles, printing true true. bool is Copy: it lives whole in its box, so = duplicates it and both variables stay usable. This is every chapter 1–7 program, now with its license visible.
Question #2
Compile or not? One sentence of why.
fn main() {
let label = (1, String::from("crate"));
let moved = label;
println!("{}", label.0);
}Show solution
error[E0382]: the tuple contains a String, so the whole tuple moves to moved, and label can't be used afterwards, not even its innocent i32 element. One non-Copy passenger makes the whole vehicle non-Copy.
Question #3
A colleague asks: "Why doesn't Rust just make String Copy too, so all this move business disappears?" Answer in two or three sentences, using the chapter's vocabulary.
Show solution
Copying a String on every = would mean either copying just the handle, which creates two owners of one heap block and a double free at the closing brace, or copying the heap block too, which makes a cheap-looking assignment secretly cost whatever the text weighs. Rust rejects both: silent danger and silent expense. Moves keep one owner per block (safety) and keep = cheap (honesty); when you genuinely want the expensive duplicate, lesson 8.7's .clone() lets you say so out loud.
Next lesson, the move rules meet functions, several chapter-5 debts get paid at once, and the compiler starts openly advertising chapter 9.