Checkpoint: Ownership
Eight short programs, one question each, from the Ownership lessons (Stack and heap through Mutable borrows) and the parts before them. Decide first: does it compile, and what does it print? If it does not compile, which error code?
RememberAsk the same three questions of every line: who owns this value, who is borrowing it, and for how long?
Each program is a file in a Cargo project (src/bin/q1.rs and so on) and runs with cargo run --bin q1. The answers are real runs in the page’s image (Rust 1.99.1, edition 2024).
1. Which error code?
Section titled “1. Which error code?”fn consume(s: String) -> usize { s.len()}
fn main() { let s = String::from("hello"); for _ in 0..2 { println!("{}", consume(s)); }}Answer
error[E0382]: use of moved value: `s` --> src/bin/q1.rs:8:32 | 6 | let s = String::from("hello"); | - move occurs because `s` has type `String`, which does not implement the `Copy` trait 7 | for _ in 0..2 { | ------------- inside of this loop 8 | println!("{}", consume(s)); | ^ value moved here, in previous iteration of loop | note: consider changing this parameter type in function `consume` to borrow instead if owning the value isn't necessary --> src/bin/q1.rs:1:15 | 1 | fn consume(s: String) -> usize { | ------- ^^^^^^ this parameter takes ownership of the value | | | in this function help: consider cloning the value if the performance cost is acceptable | 8 | println!("{}", consume(s.clone())); | ++++++++
consume takes a String by value, so the first call moves s into it. The second trip round the loop has nothing left to pass, and the label under the arrow says so: “value moved here, in previous iteration of loop”. Borrow with &String or &str when the function only reads. See Everywhere a value can move.
2. What does it print?
Section titled “2. What does it print?”struct Noisy(&'static str);
impl Drop for Noisy { fn drop(&mut self) { println!("drop {}", self.0); }}
fn take(n: Noisy) { println!("took {}", n.0);}
fn main() { let _a = Noisy("a"); let a2 = Noisy("a2"); let _ = Noisy("b"); let _c = Noisy("c"); let _a = Noisy("shadow"); take(a2); println!("end");}Answer
$ cargo run --bin q2
drop b
took a2
drop a2
end
drop shadow
drop c
drop a
let _ = Noisy("b") binds nothing, so the value is dropped on that line. take(a2) moves a2 into the function, which drops it when it returns, before end. Shadowing hides the first _a but does not drop it. At the closing brace the remaining variables drop in reverse order: the shadowing _a, _c, then the first _a. See Scopes, drop order and let _.
3. What does it print?
Section titled “3. What does it print?”fn bump(mut x: i32, mut arr: [i32; 3], list: &mut Vec<i32>) { x += 10; arr[0] += 10; list[0] += 10; println!("inside: {x} {arr:?} {list:?}");}
fn main() { let x = 1; let arr = [1, 2, 3]; let mut list = vec![1, 2, 3]; bump(x, arr, &mut list); println!("outside: {x} {arr:?} {list:?}");}Answer
$ cargo run --bin q3
inside: 11 [11, 2, 3] [11, 2, 3]
outside: 1 [1, 2, 3] [11, 2, 3]
An i32 and an array of i32 are Copy, so bump received copies and the caller’s x and arr are unchanged. The Vec was lent with &mut, so list[0] += 10 reached the caller’s list. See Copy types and &mut: lend a value to be changed.
4. Which error code?
Section titled “4. Which error code?”fn main() { let mut v = vec![1, 2, 3]; let first = &mut v[0]; let second = &mut v[1]; *first += 10; *second += 10; println!("{v:?}");}Answer
error[E0499]: cannot borrow `v` as mutable more than once at a time --> src/bin/q4.rs:4:23 | 3 | let first = &mut v[0]; | - first mutable borrow occurs here 4 | let second = &mut v[1]; | ^ second mutable borrow occurs here 5 | *first += 10; | ------------ first borrow later used here | = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices
Both references borrow the whole Vec, not one slot, and first is still used after second is created. The compiler’s help line names a method for getting two separate pieces. See Changing two parts at once.
5. Which error code?
Section titled “5. Which error code?”fn main() { let s = String::from("hello"); let r = &s; let t = s; println!("{r} {t}");}Answer
error[E0505]: cannot move out of `s` because it is borrowed --> src/bin/q5.rs:4:13 | 2 | let s = String::from("hello"); | - binding `s` declared here 3 | let r = &s; | -- borrow of `s` occurs here 4 | let t = s; | ^ move out of `s` occurs here 5 | println!("{r} {t}"); | - borrow later used here | help: consider cloning the value if the performance cost is acceptable | 3 | let r = &s.clone(); | ++++++++
It is E0505, not E0382: s is never used after the move, but r is still borrowing it when println! reads r. A move needs no borrow to be alive. See How long a borrow lasts.
6. Which error code, and how many changes?
Section titled “6. Which error code, and how many changes?”fn add_item(items: &Vec<i32>) { items.push(0);}
fn main() { let mut items = vec![1, 2]; add_item(&items); println!("{items:?}");}Answer
error[E0596]: cannot borrow `*items` as mutable, as it is behind a `&` reference --> src/bin/q6.rs:2:5 | 2 | items.push(0); | ^^^^^ `items` is a `&` reference, so it cannot be borrowed as mutable | help: consider changing this to be a mutable reference | 1 | fn add_item(items: &mut Vec<i32>) { | +++
&Vec<i32> is a read-only borrow, and push needs a &mut. Two changes: the parameter type becomes &mut Vec<i32> and the call becomes &mut items. The mut in let mut items is already written, and it is what allows &mut items:
fn add_item(items: &mut Vec<i32>) { items.push(0);}
fn main() { let mut items = vec![1, 2]; add_item(&mut items); println!("{items:?}");}[1, 2, 0]
7. Fix it
Section titled “7. Fix it”This does not compile. What is the error code, and what do you change?
fn main() { let mut v = vec![1, 2, 3]; for x in &v { if *x == 2 { v.push(*x * 10); } } println!("{v:?}");}Answer
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/bin/q7.rs:5:13 | 3 | for x in &v { | - -- | | | | | immutable borrow occurs here | _____| immutable borrow later used here | | 4 | | if *x == 2 { 5 | | v.push(*x * 10); | | ^^^^^^^^^^^^^^^ mutable borrow occurs here 6 | | } 7 | | } | |_____- this for loop borrows `v` immutably, preventing mutation within its body | = help: consider using an index-based loop instead, or collecting modifications into a separate collection
for x in &v holds a read-only borrow of v for the whole loop, and push needs a mutable one. Looping over positions instead borrows v again on each line. 0..v.len() is worked out once, so the pushed item is not visited. v.push(v[i] * 10) is accepted because the index is read before the push starts:
fn main() { let mut v = vec![1, 2, 3]; for i in 0..v.len() { if v[i] == 2 { v.push(v[i] * 10); } } println!("{v:?}");}[1, 2, 3, 20]
8. Does it compile? What does it print?
Section titled “8. Does it compile? What does it print?”fn main() { let pair = (String::from("left"), String::from("right")); let l = pair.0; println!("{l} {}", pair.1);}Answer
$ cargo run --bin q8
left right
It compiles. Moving one field out of a tuple you own leaves the other field usable: that is a partial move. Using the whole tuple afterwards is refused. Swapping the last line for println!("{l} {pair:?}"); gives:
error[E0382]: borrow of partially moved value: `pair` --> src/bin/q8.rs:4:20 | 3 | let l = pair.0; | ------ value partially moved here 4 | println!("{l} {pair:?}"); | ^^^^ value borrowed here after partial move | = note: partial move occurs because `pair.0` has type `String`, which does not implement the `Copy` trait
A tuple’s fields are separate places, like a struct’s. See Moving out of a struct, which shows the same rule on a struct.
How to score yourself
Section titled “How to score yourself”Count a question as right when you gave the correct output, or the correct error code and the reason. The error questions use five codes that name five different problems: E0382 (use after a move), E0499 (two writers), E0502 (a writer while a reader is alive), E0505 (a move while a borrow is alive) and E0596 (writing through a &). If you mixed two of them up, compare their messages side by side: each one names what is borrowed or moved, and the line where the other use happens.
Question 2 and question 8 are about what the compiler does when nothing is wrong: the drop order and the partial move are rules, not mistakes, and they run the same way each time. Question 3 shows the difference between a value that is copied and a value that is lent.
If you missed one
Section titled “If you missed one”- Questions 1, 2, 5 and 8: Ownership and Borrowing
- Question 3: Ownership (Copy types) and Copy or reference
- Questions 4, 6 and 7: Mutable borrows
- Stack, heap and why a move is cheap: Stack and heap
The next part of the course starts with Structs, and ends with Checkpoint: Data.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.