Lesson 6.2 · 9 min
Walking a list
A history is only useful if you can look through it: print every message, number them, add up their lengths. Walking a list is also where the one-owner rule shows up again, because a loop can either borrow the items or take them.
Adds to ChatterboxPrint the history.
Loop and keep the list
You have used for over a fixed array of lines. A Vec works the same way, with one question the array did not raise: after the loop, do you still have the list?
That depends on what you hand to for. If you give it the Vec itself, the loop takes the Vec: it becomes the owner and hands you the items one at a time. If you give it &history (a borrow, from chapter 4), the loop only looks at the items, and the Vec stays yours.
error[E0382]: borrow of moved value: `history` --> src/main.rs:11:29 | 7 | let history = vec![Message { author: "sam".to_string(), text: "hi".to_string() }]; | ------- move occurs because `history` has type `Vec<Message>`, which does not implement the `Copy` trait 8 | for m in history { | ------- `history` moved due to this implicit call to `.into_iter()` ... 11 | println!("{} messages", history.len()); | ^^^^^^^ value borrowed here after move | note: the `for` loop is desugared into a call to `std::iter::IntoIterator::into_iter`, which takes ownership of the receiver `self`, which moves `history` --> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/iter/traits/collect.rs:312:17 help: consider iterating over a slice of the `Vec<Message>`'s content to avoid moving into the `for` loop | 8 | for m in &history { | +
The compiler says "borrow of moved value: history". Read it as: you gave history to the loop, so it is not yours any more, and then you asked it for its length. The fix is one character. Write for m in &history and the loop borrows instead.
$ cargo run
sam: hi
1 messagesNumbering with enumerate
history.iter() is the same borrowing walk written as a method. Add .enumerate() and each step hands you two things: the position and the item. Write them as a pair, (i, m). Positions start at 0, so print i + 1 for people.
Chains that wait
iter() does not do the walk. It returns an iterator: a thing that knows how to produce the next item when someone asks. You can add steps to it, for example .map(|m| m.text.len()), which says "turn each message into its length". The |m| … part is a tiny function written in place; as a preview, the lesson two from now explains it properly. For now read it as "given m, produce this".
Here is the surprising part. A chain of steps does nothing until something at the end asks for the results: .sum(), .count() or .collect(), or a for loop. Rust calls these steps lazy. Work happens when the answer is requested, not when the chain is written.
What is the FIRST line this program prints?
looking at samchain builttotal 7
Reveal the answer
The program prints: chain built Building the chain with map does not run the little function at all. "chain built" prints first. Only when sum() asks for the answer does Rust walk the list, printing "looking at sam", then "looking at ada", then the total is 7.
Chatterbox v0.6: /history
Add a /history command that prints every message with a number, and finish by adding up every message's length with the lazy chain you just saw. Notice that both walks borrow, so the history is still there afterwards, and the program can keep taking new messages.
$ cargo run
1. sam: hello there
2. sam: anyone home?
2 messages, 23 charactersWrite numbered. For each message return one line in the form 1. sam: hello: the position starting at 1, the author, a colon and a space, then the text.
Write numbered. For each message return one line in the form 1. sam: hello: the position starting at 1, the author, a colon and a space, then the text.
Hidden tests (4): empty_history_gives_no_lines history_is_still_usable_after one_message numbers_follow_the_order
This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.
What you have now
You can walk a history without losing it: for m in &history borrows, for m in history takes. You can number items with enumerate, and you know a chain such as iter().map(…).sum() only runs when its last step asks. Next: counting messages per user, which needs a different kind of container.
Say it out loud, then reveal
What is the difference between for m in history and for m in &history?Reveal
What does enumerate give you at each step?Reveal
Why does history.iter().map(...) print nothing by itself?Reveal
That is lesson 6.2. Tick it off so the course remembers where you are.
Questions people ask
What is E0382 in Rust?
Use of a moved value. You gave a value away (here, to a for loop) and then tried to use it again.
Should I write for m in &history or for m in history.iter()?
They do the same thing. Both borrow the items. The &history form is shorter and most common.
What does collect do?
It runs an iterator chain and gathers the results into a collection such as a Vec.