Lesson 8.6 · 8 min
Chapter review: Organising code
This chapter split Chatterbox into files, taught it to remember across restarts, wrote one function for any kind of list, and let you swap who replies while you chat. Before the bot talks to a real model, check that it has all stuck: a mixed quiz, then one bigger exercise that uses the chapter together.
Adds to ChatterboxNothing new: a mixed quiz on the whole chapter, and a bigger exercise that uses all of it.
What this chapter gave Chatterbox
Chatterbox v0.8 is five files instead of one: main.rs keeps the chat loop, and message.rs, command.rs, store.rs and replier.rs each hold one job. Quit and start it again and the conversation is still there, read back from chatterbox.log. /last uses one last_n that works for any list, and /model pretend swaps who answers in the middle of a chat. Here is the chapter, one line per lesson. If a line feels shaky, the link takes you back.
- 8.1 Splitting code into modules:
modmakes a named box, everything inside is private until markedpub,usebrings a name in, andmod message;means "the box is inmessage.rs". - 8.2 Saving and loading:
fs::writeandfs::read_to_stringcan fail with anio::Error; a missing file on the first run just means "no history yet", andmaincan return aResultso?works inside it. - 8.3 Code for any type: generics:
<T>leaves a blank for a type the caller chooses. The body may only do what works for everyT, unless a bound such asT: PartialOrdasks for more. - 8.4 Traits: a trait is a named promise of methods,
impl Replier for Echokeeps it, andBox<dyn Replier>holds any type that keeps it, chosen while the program runs. - 8.5 How long a borrow lasts: a borrow must not outlive its owner. With one borrowed input Rust works out where a returned borrow comes from; with two, or none, you name it with
'a.
Mixed quiz
Seven short programs, all new, with the ideas shuffled on purpose: a module, then a generic, then a lifetime, then a file. Mixing them is harder than seven of the same kind in a row, and that is what makes it stick. For each one, decide first: does it compile? If it does, what exactly does it print? Every answer was produced by the real compiler.
What happens when this program is compiled?
- It compiles and runs
- Compile error E0603
- Compile error E0616
Reveal the answer
It does not compile: E0603. Command is pub, but parse is not, so it is private to the command box and main may not call it. That is E0603, "function is private". E0616 is the same idea for a struct field. Lesson 8.1 has the rule; the fix is pub fn parse.
error[E0603]: function `parse` is private --> src/main.rs:17:20 | 17 | match command::parse("/help") { | ^^^^^ private function | note: the function `parse` is defined here --> src/main.rs:7:5 | 7 | fn parse(line: &str) -> Option<Command> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
What does this print?
241
Reveal the answer
The program prints: 2 Here T is &str, and the bound T: PartialEq is what allows == inside the body. Text compares exactly: Hi has a capital and hi has a space, so only the two plain hi messages count. Lesson 8.3 covers bounds.
What happens when this program is compiled?
- It compiles and runs
- Compile error E0106
- Compile error E0597
Reveal the answer
It does not compile: E0106. The answer can borrow from either input, the history or the fallback, and with two borrowed inputs no elision rule says which. So the signature is missing a lifetime: E0106. Naming one, 'a, on both inputs and the output fixes it. Lesson 8.5 walks through the same repair.
error[E0106]: missing lifetime specifier --> src/main.rs:1:53 | 1 | fn latest_or(history: &[String], fallback: &str) -> &str { | --------- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `history` or `fallback` help: consider introducing a named lifetime parameter | 1 | fn latest_or<'a>(history: &'a [String], fallback: &'a str) -> &'a str { | ++++ ++ ++ ++
There is no file called no-such-chat.log. What does this print?
no history yetloaded 0 bytescould not read: No such file or directory (os error 2)
Reveal the answer
The program prints: no history yet Reading a file that is not there is an Err, not an empty text, so the first arm is out. The second arm has a guard, and its condition is true because the kind of error is NotFound, so it wins before the third arm is tried. That is how Chatterbox treats its first run in lesson 8.2.
What is the second line this program prints?
shout: HI THEREecho: you said: hi thereshout: you said: HI THERE
Reveal the answer
The program prints: shout: HI THERE Each Box<dyn Replier> remembers which type is inside it, so r.reply runs that type's own method, looked up while the program runs. The second box holds a Shout, whose reply is just the message in capitals. Lesson 8.4 shows why the list needs dyn.
What happens when this program is compiled?
- It compiles and runs
- Compile error E0277
- Compile error E0369
Reveal the answer
It does not compile: E0277. The compiler checks announce for every type T could be, and not every type can be printed with {}. So it says T does not implement Display: E0277, a trait bound that is not satisfied. Writing T: Display asks for the ability, as in lessons 8.3 and 8.4.
error[E0277]: `T` doesn't implement `std::fmt::Display` --> src/main.rs:2:20 | 2 | println!("new: {item}"); | ^^^^^^ `T` cannot be formatted with the default formatter | = note: in format strings you may be able to use `{:?}` (or `{:#?}` for pretty-print) instead help: consider restricting type parameter `T` with trait `Display` | 1 | fn announce<T: std::fmt::Display>(item: T) { | +++++++++++++++++++
What happens when this program is compiled?
- It compiles and runs
- Compile error E0616
- Compile error E0603
Reveal the answer
It compiles and runs. main never touches the private text field. It calls shout, which is pub and lives inside the message box, where private things are allowed. It prints you: HI. That is the "show it on your terms" fix from lesson 8.1.
Milestone: a module of repliers
Now use the chapter together. Below is a small program, separate from Chatterbox, built the way Chatterbox is. A module, replier, holds a Replier trait and two types that keep it. You fill in three bodies:
Shout'sreply: the message in capitals with a!at the end (lesson 8.4).pick: the box for a name it knows,Nonefor any other. It ispubsomaincan call it, and its answer is anOption<Box<dyn Replier>>, which is how one function can hand back either type (lessons 8.1 and 8.4).lines_from: the text of every history line a given author wrote. Its signature is already written, and the'ain it says the answer borrows from the history and not fromwho. One hidden test throwswhoaway before it uses the answer, and that only compiles because of that signature (lesson 8.5).
Saving to a file has no place here: the online Playground keeps no files between runs, so fs is a thing to try on your own computer with Chatterbox itself.
Fill in Shout's reply, pick and lines_from. pick("shout") gives a replier whose reply to hi is HI!, pick("banana") gives None, and lines_from with "you" gives the text of your lines only, in order.
Fill in Shout's reply, pick and lines_from. pick("shout") gives a replier whose reply to hi is HI!, pick("banana") gives None, and lines_from with "you" gives the text of your lines only, in order.
Hidden tests (9): an_unknown_name_is_none an_empty_history_has_no_lines a_colon_inside_the_text_survives pick_knows_echo pick_knows_shout shout_answers_in_capitals the_author_must_match_exactly who_can_go_before_the_answer_is_used your_lines_in_order
This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.
What you have now
If the quiz and the milestone went well, you can put code in boxes and choose what each one shows, write one function for many types and say what those types must be able to do, give several types one promise and swap between them while the program runs, and read a lifetime as a description of where a borrow comes from. As a preview, the next chapter gives Chatterbox a replier that is not pretending.
Say it out loud, then reveal
An item inside a <code>mod</code> has no <code>pub</code>. Who can use it?Reveal
Why is <code>{item}</code> refused inside <code>fn announce<T>(item: T)</code>?Reveal
{}. The bound T: Display narrows T to types that can.When do you need <code>Box<dyn Replier></code> instead of a generic <code>T: Replier</code>?Reveal
pick or /model, or when different types must sit in one list. A generic fixes the type when the program is compiled.In <code>fn lines_from<'a>(history: &'a [String], who: &str) -> Vec<&'a str></code>, why does <code>who</code> have no <code>'a</code>?Reveal
who out of the lifetime lets the caller throw it away while still using the answer.Your Chatterbox so far: v0.8 (after lesson 8.4)
Download this version (zip)Unzip, then cargo run inside the folder.
That is lesson 8.6. Tick it off so the course remembers where you are. That is also the end of chapter 8: 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 is the difference between E0603 and E0616 in Rust?
Both mean something is private to its module. E0603 is a private item, such as a function or struct, used from outside. E0616 is a private field of a struct read from outside. Add pub, or offer a pub method instead.
When should I use generics and when should I use dyn Trait in Rust?
Use a generic with a bound when the type is known where the code is compiled: it costs nothing while running. Use Box dyn Trait when the type is only known while the program runs, or when values of different types must share one list.
Why does Rust say missing lifetime specifier on my function?
The function returns a reference and has two reference inputs, or none, so Rust cannot tell where the answer borrows from. Name a lifetime and put it on the output and on the input the answer really comes from.
Why does my file not load in the Rust Playground?
The online Playground keeps no files between runs. Run programs that save and load files on your own computer with cargo run.
