Step 3 of 6
Moves: one owner at a time
Copying an integer is easy:
fn main() {
let a = 5;
let b = a;
println!("a = {}, b = {}", a, b);
}
a = 5, b = 5
b gets its own copy of the 4 bytes, and both variables can be used. Now try the same with a String:
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{} {}", s1, s2);
}
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:4:23
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 | println!("{} {}", s1, s2);
| ^^ value borrowed here after move
What happened? let s2 = s1; copies the handle (pointer, length, capacity), not the heap text. If Rust stopped there, two variables would point at the same text:
s1 s2
┌────────────┐ ┌────────────┐
│ ptr ───────┼──┐ ┌──┼──── ptr │
│ len 5 │ │ │ │ len 5 │
│ cap 5 │ │ │ │ cap 5 │
└────────────┘ ▼ ▼ └────────────┘
┌───┬───┬───┬───┬───┐
HEAP │ h │ e │ l │ l │ o │
└───┴───┴───┴───┴───┘
At the end of main both would be dropped, and the same heap block would be freed twice: a double free, one of the bugs that corrupt memory in C programs. Rule 2 forbids it: a value has one owner at a time. So let s2 = s1; moves ownership from s1 to s2, and s1 can't be used any more. Only s2 will free the text.
s1 (moved out: unusable) s2
┌────────────┐ ┌────────────┐
│ ░░░░░░░░░░ │ │ ptr ───────┼──┐
│ ░░░░░░░░░░ │ │ len 5 │ │
│ ░░░░░░░░░░ │ │ cap 5 │ │
└────────────┘ └────────────┘ ▼
┌───┬───┬───┬───┬───┐
HEAP │ h │ e │ l │ l │ o │
└───┴───┴───┴───┴───┘
Read the error message again with that picture in mind. It names the type (String, which "does not implement the Copy trait": next step), the line where the value moved, and the line where you used it afterwards. It says "borrow" because println! borrows its arguments to print them; borrowing is the next module. The full message also suggests .clone(), which is one fix; step 4 covers when it's the right one.
A move is cheap: only the 24-byte handle is copied, however long the text is. Nothing on the heap is touched.
Use the new owner
Usually the fix is to keep using the value through its new owner, or to finish with the old name before the move:
fn main() {
let draft = String::from("Patch notes v1.2");
println!("draft: {}", draft); // use it before the move...
let published = draft; // ...ownership moves here
println!("published: {}", published);
}
draft: Patch notes v1.2
published: Patch notes v1.2
Where moves happen
Any time a value that isn't Copy (like String and Vec, and most types you'll write) is handed to something else:
- assigning it:
let b = a; - passing it to a function (step 5)
- pushing it into a vector:
names.push(name); - looping over a vector with
for x in v: the loop takes ownership ofvand hands out its elements one by one. After the loop,vis gone.
fn main() {
let names = vec![String::from("Ana"), String::from("Ben")];
for name in names {
println!("{}", name);
}
println!("{} names", names.len());
}
error[E0382]: borrow of moved value: `names`
--> src/main.rs:6:26
|
3 | for name in names {
| ----- `names` moved due to this implicit call to `.into_iter()`
...
6 | println!("{} names", names.len());
| ^^^^^ value borrowed here after move
Fix it by finishing with names before the loop (let count = names.len();), or by looping over a reference with for name in &names, which the compiler suggests and which you'll learn in the next module.
Reusing a moved-from variable
Moving out of a variable leaves it empty, not destroyed. If it's mut, you can give it a new value and use it again:
fn main() {
let mut current = String::from("level 1");
let finished = current;
current = String::from("level 2");
println!("finished {}, now playing {}", finished, current);
}
finished level 1, now playing level 2
Common mistakes
- Using a value after moving it (E0382). Find the "value moved here" line in the error, then use the new owner, or reorder so the old name is used first.
- Moving inside a loop.
let bag = item;inside a loop movesitemin the first round, so the next round fails withuse of moved value: `item`and the notevalue moved here, in previous iteration of loop. - Thinking a move is expensive. It copies three numbers. The text stays where it is.
Your task
The program uses item after it moved into backpack. Fix it without .clone() and without creating a second String: reorder the lines or use the new owner, so it prints:
Picked up: health potion
Backpack holds: health potion
Challenge 2
The for loop takes ownership of orders, so orders.len() after the loop doesn't compile. Without .clone(), fix it by counting the orders before the loop, so the program prints:
Cooking #101 pizza
Cooking #102 soup
Cooking #103 salad
3 orders done
Challenge 3
Swap what the two hands hold, using moves only: no .clone(), no new strings. You'll need a third variable to hold one of the values for a moment (moving out of left leaves it empty, and since it's mut, you can move a new value into it). The program should print:
left hand: shield, right hand: sword