Lesson 6.6 · 8 min
Chapter review: Collections
Chatterbox remembers the whole conversation now: it can number it, count it, search it, keep facts by name and find your favourite words. Before chapter 7 teaches it to report errors, check that the five ideas behind that are solid: a mixed quiz, then one bigger exercise that needs all of them.
Adds to ChatterboxNothing new: a mixed quiz on the whole chapter, and a bigger exercise that uses all of it.
What chapter 6 gave Chatterbox
At the end of chapter 5, Chatterbox had good types but forgot every message as soon as it answered it. Now it keeps them: a numbered /history, /count, /remember and /recall, /find and /stats. Five ideas did that:
- 6.1 Vec: a growable list that owns its items;
[ ]crashes on a missing position,getgives anOption. - 6.2 walking a list:
for m in &historyborrows and keeps the list;enumerateadds the position, counting from 0. - 6.3 HashMap: rows of key and value, found by name;
entryandor_insertcount things in one line; no order. - 6.4 closures: small functions written on the spot that capture the names around them, by borrowing them.
- 6.5 iterators: a value with a
next(), wrapped in steps likefilterandmap, that does nothing until something at the end asks.
Nothing new comes in this lesson. Every answer below was produced by the real compiler, so a wrong guess points straight at the lesson to reopen.
Mixed quiz
The questions jump between the five ideas on purpose, because a real program uses them all at once.
What does this print?
only 2 messagesthird: helloit panics
Reveal the answer
The program prints: only 2 messages Positions count from 0, so a two-item list has positions 0 and 1, and 2 is past the end. get never crashes: it answers None, and the None arm runs. history[2] would have panicked instead. Lesson 6.1.
Does this compile?
- It compiles and runs
- Compile error E0502
- Compile error E0382
Reveal the answer
It does not compile: E0502. The closure captures history by borrowing it to look, and that borrow lasts until its last call, on the final line. push needs an exclusive borrow in the middle of that stretch, so the compiler refuses. Call count() before the push, or make the closure after it. Lesson 6.4, with the rule from lesson 4.4.
error[E0502]: cannot borrow `history` as mutable because it is also borrowed as immutable --> src/main.rs:4:5 | 3 | let count = || history.len(); | -- ------- first borrow occurs due to use of `history` in closure | | | immutable borrow occurs here 4 | history.push(String::from("hello")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here 5 | println!("{}", count()); | ----- immutable borrow later used here
What does this print?
2 Some(3)4 Some(1)2 Some(1)
Reveal the answer
The program prints: 2 Some(3) Every word is lowercased before it becomes a key, so the three greetings land in one row and bye in another: two rows. entry(…).or_insert(0) makes the row at 0 the first time and the += 1 adds to it every time, so that row reaches 3. Lesson 6.3.
Does this compile?
- It compiles and runs
- Compile error E0596
- Compile error E0382
Reveal the answer
It does not compile: E0596. iter() lends each message to look at, and push wants to change it, which a looking borrow cannot do. The walk that lends each item to change is iter_mut() (or &mut history). Making history itself mut is not enough: the kind of walk decides. Lesson 6.5 has the table of all three walks.
error[E0596]: cannot borrow `*m` as mutable, as it is behind a `&` reference --> src/main.rs:4:9 | 3 | for m in history.iter() { | -------------- this iterator yields `&` references 4 | m.push('!'); | ^ `m` is a `&` reference, so it cannot be borrowed as mutable | help: use mutable method | 3 | for m in history.iter_mut() { | ++++
What does this print?
123
Reveal the answer
The program prints: 1 enumerate numbers from 0, so "ok" is 0 and "sure thing", the first one longer than 3 bytes, is 1. break leaves the loop right there, so "absolutely" is never looked at. If you said 2, you counted from 1: lesson 6.2.
What does this print?
["jo", "sam", "ada", "alexandra"]["jo", "ada", "sam", "alexandra"]["alexandra", "sam", "ada", "jo"]
Reveal the answer
The program prints: ["jo", "sam", "ada", "alexandra"] The closure hands sort_by_key each name's length, and it sorts by that, shortest first. "sam" and "ada" both have 3 letters, and items with equal keys keep the order they had, so "sam" stays in front. It is not alphabetical: only the length was compared. Lesson 6.4.
Does this compile?
- It compiles and runs
- Compile error E0283
- Compile error E0382
Reveal the answer
It does not compile: E0283. sum(), like collect(), can build more than one type, and nothing here says which: total has no type. The compiler stops with "type annotations needed" and even suggests let total: usize. Lesson 6.5 met the same error on collect().
error[E0283]: type annotations needed --> src/main.rs:3:9 | 3 | let total = history.iter().map(|m| m.len()).sum(); | ^^^^^ --- type must be known at this point | = note: the type must implement `Sum<usize>` help: the trait `Sum` is implemented for `usize` --> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/iter/traits/accum.rs:50:8 ::: /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/iter/traits/accum.rs:206:0 | = note: in this macro invocation note: required by a bound in `std::iter::Iterator::sum` --> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/iter/traits/iterator.rs:3665:4 = note: this error originates in the macro `integer_sum_product` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider giving `total` an explicit type, where the type for type parameter `S` is specified | 3 | let total: usize = history.iter().map(|m| m.len()).sum(); | +++++++
What does this print?
HELLO HOWDYHI HELLOHELLO HOWDY HIYA THERE
Reveal the answer
The program prints: HELLO HOWDY Each item goes through every step in order: filter drops "hi" and "hey", map capitalises what is left, and take(2) stops after two, so "hiya there" is never even looked at. collect() knows to build a Vec<String> from the type on loud. Lesson 6.5.
Milestone: who talks most, and where
Time to use all five ideas together. Imagine Chatterbox in a group chat, with several people in the history. A /digest command would want two answers: who has said the most, and where a word came up. Write the three functions behind it. They take the history as &[Message], a borrowed view of the list, like every exercise in this chapter.
The doc comments (the /// lines) say exactly what each must return. One part of leaderboard needs care: a HashMap has no order, so its rows have to be put into a Vec and sorted, with a rule for ties, or the answer could change from run to run. Lesson 6.5 sorted /stats exactly this way.
Fill in the three todo!() bodies. tally counts messages per author in a HashMap. leaderboard turns that count into lines like sam: 3, most first, ties in alphabetical order. search gives the /history numbers of the messages that contain a word, ignoring capitals. The hidden tests use a six-message chat, ties, an empty history and a word nobody said.
Fill in the three todo!() bodies. tally counts messages per author in a HashMap. leaderboard turns that count into lines like sam: 3, most first, ties in alphabetical order. search gives the /history numbers of the messages that contain a word, ignoring capitals. The hidden tests use a six-message chat, ties, an empty history and a word nobody said.
Hidden tests (8): an_empty_history_has_no_rows leaderboard_puts_the_most_first leaderboard_ties_are_alphabetical search_ignores_capitals_on_both_sides search_with_no_hits_is_empty search_numbers_from_one tally_counts_each_author the_history_is_only_borrowed
This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.
Where to go from here
If a quiz answer surprised you, reopen the lesson its explanation names before going on. Chapter 7 keeps all of this and asks a new question: what should a function return when it can fail? So far a missing item was an Option and nothing more. Next, a failure gets to say why.
Say it out loud, then reveal
When do you reach for a Vec, and when for a HashMap?Reveal
What do <code>iter()</code>, <code>iter_mut()</code> and <code>into_iter()</code> each hand you?Reveal
How does a closure use a name from around it, and how long does that last?Reveal
Why does <code>let total = v.iter().map(…).sum();</code> fail, and how do you fix it?Reveal
sum() can produce more than one type and nothing says which. Give the name a type, as in let total: usize.Your Chatterbox so far: v0.6 (after lesson 6.5)
Download this version (zip)Unzip, then cargo run inside the folder.
That is lesson 6.6. Tick it off so the course remembers where you are. That is also the end of chapter 6: a few minutes with the course flashcards now makes it stick, and the chapter’s cheat sheet has every idea on one screen.
Go deeper
Questions people ask
What should I know before learning Rust error handling?
How to keep values in a Vec and a HashMap, how to walk them with a for loop or an iterator chain without giving them away, and how Option works, because Result in chapter 7 is built the same way as Option.
What is the difference between iter and into_iter in Rust?
iter lends each item to look at, so the collection is still there afterwards. into_iter hands over each item itself, so the collection is used up by the walk.
Why does a HashMap print its keys in a different order each time?
A HashMap keeps no order, and the order it walks its rows in can change from run to run. If you need a stable order, copy the rows into a Vec and sort it.
Do Rust iterator chains run when you write them?
No. Steps like map and filter only build a bigger iterator. The work happens when something at the end, such as collect, sum, count or a for loop, asks for the items, and then each item goes through every step in turn.
