Skip to content
Learn Rust
Theme

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).

Rustsrc/bin/q1.rs
fn consume(s: String) -> usize {
s.len()
}
fn main() {
let s = String::from("hello");
for _ in 0..2 {
println!("{}", consume(s));
}
}
Answer
$ cargo run --bin q1
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.

Rustsrc/bin/q2.rs
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
terminal
$ 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 _.

Rustsrc/bin/q3.rs
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
terminal
$ 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.

Rustsrc/bin/q4.rs
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
$ cargo run --bin q4
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.

Rustsrc/bin/q5.rs
fn main() {
let s = String::from("hello");
let r = &s;
let t = s;
println!("{r} {t}");
}
Answer
$ cargo run --bin q5
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?”
Rustsrc/bin/q6.rs
fn add_item(items: &Vec<i32>) {
items.push(0);
}
fn main() {
let mut items = vec![1, 2];
add_item(&items);
println!("{items:?}");
}
Answer
$ cargo run --bin q6
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:

Rustq6.rs
fn add_item(items: &mut Vec<i32>) {
items.push(0);
}
fn main() {
let mut items = vec![1, 2];
add_item(&mut items);
println!("{items:?}");
}
$ cargo run --bin q6
[1, 2, 0]

See &mut: lend a value to be changed.

This does not compile. What is the error code, and what do you change?

Rustsrc/bin/q7.rs
fn main() {
let mut v = vec![1, 2, 3];
for x in &v {
if *x == 2 {
v.push(*x * 10);
}
}
println!("{v:?}");
}
Answer
$ cargo run --bin q7
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:

Rustq7.rs
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:?}");
}
$ cargo run --bin q7
[1, 2, 3, 20]

See Changing a list while looping over it.

Rustsrc/bin/q8.rs
fn main() {
let pair = (String::from("left"), String::from("right"));
let l = pair.0;
println!("{l} {}", pair.1);
}
Answer
terminal
$ 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:

$ cargo run --bin q8
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.

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.

The next part of the course starts with Structs, and ends with Checkpoint: Data.