Skip to content
Learn Rust
Theme

Checkpoint: Collections

This checkpoint is for the Collections part of the course (looping over collections, closures, iterators) and the parts before it. Each question is a short program. Read it, decide your answer on paper, and only then open the answer. Every answer is a real run of that exact file with Rust 1.99.1 in the course image, so the compiler text is what you would see.

RememberGuess first. If you cannot say what a program prints, say which line you are unsure about; that line is what to re-read.

Rustsrc/bin/q1.rs
fn main() {
let v = vec![1, 2, 3, 4];
let mut evens_doubled = v
.iter()
.map(|x| {
println!("map {x}");
x * 2
})
.filter(|x| x % 4 == 0);
println!("start");
println!("{:?}", evens_doubled.next());
println!("{:?}", evens_doubled.next());
}

Does it compile? What does it print, in order?

Answer
terminal
$ cargo run --bin q1
start
map 1
map 2
Some(4)
map 3
map 4
Some(8)

Building the chain prints nothing. Each next() pulls one item at a time through map and then filter, and keeps pulling until an item passes: 1 doubles to 2 and is dropped by the filter, then 2 doubles to 4 and comes out. The second next() continues from 3. See What an iterator is and Watching a chain, and running totals.

Rustsrc/bin/q2.rs
fn main() {
let mut numbers = vec![1, 2, 3, 4, 5].into_iter();
let small: Vec<i32> = numbers.by_ref().take_while(|&n| n < 3).collect();
let rest: Vec<i32> = numbers.collect();
println!("{small:?} {rest:?}");
}

Does it compile? What does it print?

Answer
terminal
$ cargo run --bin q2
[1, 2] [4, 5]

take_while has to pull 3 to find out that it fails the test, and a pulled item is gone from the iterator. by_ref() lent the iterator out, so numbers carries on from 4. See An iterator can only be used once.

Rustsrc/bin/q3.rs
fn main() {
let mut count = 0;
let mut bump = || {
count += 1;
count
};
bump();
bump();
println!("{count}");
let mut n = 10;
let mut add = move || {
n += 1;
n
};
println!("{} {}", add(), add());
println!("{n}");
}

Does it compile? What are the three lines of output?

Answer
terminal
$ cargo run --bin q3
2
11 12
10

The first closure borrows count and changes the real variable, so it ends at 2. move gives the second closure its own copy of n, because a number is copied, and the two calls change that copy to 11 and then 12. The outer n is still 10. See What a closure captures and move: take ownership.

Rustsrc/bin/q4.rs
fn main() {
let letters = ['a', 'b', 'c'];
for (i, c) in letters.iter().rev().enumerate() {
println!("{i} {c}");
}
for (i, c) in letters.iter().enumerate().rev() {
println!("{i} {c}");
}
}

Both loops compile: a slice iterator can run from the back, which rev needs, and enumerate keeps that ability when it wraps one. What are the six lines? Do both loops print the same pairs?

Answer
terminal
$ cargo run --bin q4
0 c
1 b
2 a
2 c
1 b
0 a

The order of the adaptors matters. rev().enumerate() reverses first and then counts the items it is handed, so c gets the number 0. enumerate().rev() attaches the positions first and then walks backwards, so c keeps 2. See Position and backwards.

Question 5: an adder that cannot leave home

Section titled “Question 5: an adder that cannot leave home”
Rustsrc/bin/q5.rs
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
|x| x + n
}
fn main() {
let add5 = make_adder(5);
println!("{}", add5(1));
}

Does it compile? If not, which error code, and what does the compiler suggest?

Answer
$ cargo run --bin q5
error[E0373]: closure may outlive the current function, but it borrows `n`, which is owned by the current function
 --> src/bin/q5.rs:2:5
  |
2 |     |x| x + n
  |     ^^^     - `n` is borrowed here
  |     |
  |     may outlive borrowed value `n`
  |
note: closure is returned here
 --> src/bin/q5.rs:2:5
  |
2 |     |x| x + n
  |     ^^^^^^^^^
help: to force the closure to take ownership of `n` (and any other referenced variables), use the `move` keyword
  |
2 |     move |x| x + n
  |     ++++

Without move, the closure borrows n, which belongs to make_adder and is gone when the function returns. The compiler suggests move, which makes the closure own its own n. See Returning closures.

Rustsrc/bin/q6.rs
fn main() {
let doubled = (1..4).map(|x| x * 2).collect();
println!("{}", doubled.len());
}

Does it compile? If not, which error code?

Answer
$ cargo run --bin q6
error[E0282]: type annotations needed
 --> src/bin/q6.rs:2:9
  |
2 |     let doubled = (1..4).map(|x| x * 2).collect();
  |         ^^^^^^^
3 |     println!("{}", doubled.len());
  |                    ------- type must be known at this point
  |
help: consider giving `doubled` an explicit type
  |
2 |     let doubled: Vec<_> = (1..4).map(|x| x * 2).collect();
  |                ++++++++

collect can build many kinds of collection, and nothing in the program says which one. The message points at doubled and says its type must be known at .len(), because len could mean any of them. Vec<_> on the let is enough, with _ left for Rust to work out. See Consumers: collect.

Rustsrc/bin/q7.rs
fn main() {
let name = String::from("Ada");
let consume = || drop(name);
consume();
consume();
}

Does it compile? If not, which error code, and which trait does the message name?

Answer
$ cargo run --bin q7
error[E0382]: use of moved value: `consume`
 --> src/bin/q7.rs:5:5
  |
4 |     consume();
  |     --------- `consume` moved due to this call
5 |     consume();
  |     ^^^^^^^ value used here after move
  |
note: closure cannot be invoked more than once because it moves the variable `name` out of its environment
 --> src/bin/q7.rs:3:27
  |
3 |     let consume = || drop(name);
  |                           ^^^^
note: this value implements `FnOnce`, which causes it to be moved when called
 --> src/bin/q7.rs:4:5
  |
4 |     consume();
  |     ^^^^^^^

The closure moves name out of itself when it runs (drop(name)), so it can run once; that is FnOnce. Calling it consumes it, so the second call uses a moved value. See Fn, FnMut and FnOnce.

Question 8: adding to a list while looping

Section titled “Question 8: adding to a list while looping”
Rustsrc/bin/q8.rs
fn main() {
let mut items = vec![1, 2, 3];
for x in &items {
if *x == 2 {
items.push(20);
}
}
println!("{items:?}");
}

This does not compile. Which error code? Change the program so that it prints [1, 2, 3, 20] (there is more than one way).

Answer
$ cargo run --bin q8
error[E0502]: cannot borrow `items` as mutable because it is also borrowed as immutable
 --> src/bin/q8.rs:5:13
  |
3 |       for x in &items {
  |       -        ------
  |       |        |
  |       |        immutable borrow occurs here
  |  _____|        immutable borrow later used here
  | |
4 | |         if *x == 2 {
5 | |             items.push(20);
  | |             ^^^^^^^^^^^^^^ mutable borrow occurs here
6 | |         }
7 | |     }
  | |_____- this for loop borrows `items` immutably, preventing mutation within its body
  |
  = help: consider using an index-based loop instead, or collecting modifications into a separate collection

for x in &items keeps a borrow of items for the whole loop, and push needs to change it. One fix is to collect the additions in a separate vector and add them after the loop:

Rustsrc/bin/q8_fixed.rs
fn main() {
let mut items = vec![1, 2, 3];
let mut extra = Vec::new();
for x in &items {
if *x == 2 {
extra.push(20);
}
}
items.extend(extra);
println!("{items:?}");
}
$ cargo run --bin q8_fixed
[1, 2, 3, 20]

A second fix keeps one vector and loops over positions, so no borrow lasts across the push; the compiler’s own help line names both ideas:

Rustsrc/bin/q8_index.rs
fn main() {
let mut items = vec![1, 2, 3];
for i in 0..items.len() {
if items[i] == 2 {
items.push(20);
}
}
println!("{items:?}");
}
$ cargo run --bin q8_index
[1, 2, 3, 20]

See Changing a list while looping over it.

Count a question as right only if you named the code or the output lines before opening the answer. Six or more of eight right means you can move on; fewer means the lesson below is worth another pass, this time with the example open next to the page.

The next checkpoint covers traits, generics, lifetimes and modules.