Lesson 7.5 · 8 min
Chapter review: Errors and tests
This chapter taught Chatterbox to say what went wrong instead of crashing, gave it tests that check its sentences for you, and ran the tools that tidy its code. Before the program splits into several files, check that it has all stuck: a mixed quiz, then one bigger exercise that uses the whole chapter at once.
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.7 can read /last 3, and when you type /last two it tells you what is wrong in a plain sentence and keeps listening. Every one of those sentences lives in one place, seven tests check them each time the code changes, and clippy has tidied every line. Here is the chapter, one line per lesson. If a line feels shaky, the link takes you back.
- 7.1 Operations that can fail: Result: a function that can fail returns
Ok(value)orErr(reason), andmatchmakes you say what happens in both cases. Rust has no exceptions. - 7.2 The ? operator:
?takes the value out of anOk, or returns theErrfrom the function right away. Your own error enum, with aDisplayblock, turns each kind of problem into a sentence. - 7.3 Writing tests: a
#[test]function passes if it finishes and fails if it panics;assert_eq!,assert!andmatches!are tidy ways to panic.cargo testruns them all;cargo runnever does. - 7.4 Tools that check your code:
cargo fmtlays code out one standard way,cargo clippysuggests the usual way to write something, anddbg!shows a value and hands it straight back.
Mixed quiz
Eight short programs, all new, with the ideas shuffled on purpose: a Result, then a ?, then a test, then a tool. Mixing them is harder than eight 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, or does it stop with a panic? Every answer was produced by the real compiler.
What does this print?
showing the last 12bot: '12x' is not a countshowing the last 0
Reveal the answer
The program prints: bot: '12x' is not a count parse does not pick out the digits it likes and ignore the rest: the whole text has to be a number, and 12x is not, so you get an Err. The match runs the Err arm and nothing crashes. Revisit lesson 7.1 if you expected 12.
What happens when this program is compiled?
- It compiles and runs
- Compile error E0277
- Compile error E0308
Reveal the answer
It does not compile: E0277. On an Err, ? has to return that error from the function, but count_of promises a plain usize, so there is nowhere to send it. The compiler refuses with E0277 and says ? can only be used in a function that returns a Result or an Option. Lesson 7.2 has the rule.
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`) --> src/main.rs:2:34 | 1 | fn count_of(text: &str) -> usize { | -------------------------------- this function should return `Result` or `Option` to accept `?` 2 | let n = text.parse::<usize>()?; | ^ cannot use the `?` operator in a function that returns `usize`
The test below is wrong. What does running the program (not testing it) print?
Chatterbox v0.7Chatterbox v9.9test failed
Reveal the answer
The program prints: Chatterbox v0.7 #[cfg(test)] means the tests module is only built by cargo test. Running the program leaves it out completely, so the broken promise is never even checked and main prints the banner. Run cargo test and that test would fail. Lesson 7.3 explains both lines.
What does this print?
sent 11 bytesbot: too long: 11 bytesbot: TooLong(11)
Reveal the answer
The program prints: bot: too long: 11 bytes hello there is 11 bytes, more than 5, so check returns Err(SendError::TooLong(11)). Printing it with {} uses the Display block, which picks the sentence for that variant. TooLong(11) is what the debug form {:?} would show. Lesson 7.2 builds a block like this.
What does this program print on its normal output?
bye3[src/main.rs:3:16] history.len() = 3
Reveal the answer
The program prints: bye dbg! hands its value straight back, so last is 3 minus 1, which is 2, the position of bye. Its report about history.len() goes to standard error, a separate channel, not to the normal output. Lesson 7.4 shows both.
What is the second line this program prints?
got 0truefalse
Reveal the answer
The program prints: true The first line is reading two. Then ? meets an Err and returns it from read_count on the spot, so got is never printed. Back in main, r is that Err, so is_err() prints true. Revisit lesson 7.2 if you expected a got line.
What happens when this program runs?
- It compiles and runs
- It compiles, then panics
- Compile error E0599
Reveal the answer
It compiles, then panics at run time. unwrap_err() is the mirror image of unwrap(): it gives you the reason inside an Err, and panics if the value was an Ok. "7" is a perfectly good count, so it panics. Inside a test that panic is exactly what makes the test fail. Lesson 7.3 has the table.
What happens when this program is compiled?
- It compiles and runs
- Compile error E0308
- Compile error E0277
Reveal the answer
It does not compile: E0308. The function promises a Result, and its last line hands back a plain usize. That is E0308, mismatched types. The ? took the number out of the Ok, so it has to go back into one at the end: Ok(n). Lesson 7.2 states this second rule of ?.
error[E0308]: mismatched types --> src/main.rs:5:5 | 3 | fn parse_count(text: &str) -> Result<usize, ParseIntError> { | ---------------------------- expected `Result<usize, ParseIntError>` because of return type 4 | let n = text.trim().parse::<usize>()?; 5 | n | ^ expected `Result<usize, ParseIntError>`, found `usize` | = note: expected enum `Result<usize, ParseIntError>` found type `usize` help: try wrapping the expression in `Ok` | 5 | Ok(n) | +++ +
Milestone: a /last that explains itself
Now use the whole chapter at once. Chatterbox's /last should refuse a silly request, like /last 99999, as politely as it refuses /last two. Below is a small program, separate from Chatterbox, with its own error enum, CountError, and its Display block already written. You write two functions and finish one test:
to_countturns text into a number, and turns the standard library's reason into your ownNotACount, holding the text that was wrong. That is amatchon a Result, as in lesson 7.1.last_countchecks the text after/lastin order: nothing there, then not a number, then too many. Useto_countwith a?after it, so a bad number leaves the function in one character (lesson 7.2).- The test
too_many_is_an_errorsaystodo!(). Make it check its comment's promise, the way the tests in lesson 7.3 did. The hidden tests check your two functions, and your own tests have to pass too.
If a value is not what you expect, wrap it in dbg! and run the program: the report shows up beside the output.
Write to_count and last_count so that "3" gives Ok(3), an empty or blank text gives Missing, "two" gives NotACount("two"), and anything above MAX_LAST gives TooMany. Then finish the second test.
Write to_count and last_count so that "3" gives Ok(3), an empty or blank text gives Missing, "two" gives NotACount("two"), and anything above MAX_LAST gives TooMany. Then finish the second test.
Hidden tests (10): check_a_number_with_spaces_around_it check_a_negative_number_is_not_a_count check_a_word_is_not_a_count check_one_too_many check_nothing_is_missing check_only_spaces_is_missing_too check_the_most_allowed_is_fine check_to_count_keeps_the_text a_small_number_is_fine too_many_is_an_error
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 read a failure as a value, pass it up with one character, give it a sentence, and pin that sentence down with a test that runs in a second. That is most of what keeps a growing program honest. As a preview, the next lesson starts splitting Chatterbox into several files, and these tests move with the code they check.
Say it out loud, then reveal
What does <code>?</code> do on an <code>Ok</code>, and what does it do on an <code>Err</code>?Reveal
Ok it takes the value out and the line carries on. On an Err it returns that error from the whole function straight away.Why is <code>?</code> refused inside a function that returns a plain <code>usize</code>?Reveal
Err it must return the error from the function, and a function that promises a usize has nowhere to put one. That is E0277.What decides whether a test passes?Reveal
assert_eq! or an unwrap_err() on an Ok, makes it fail.An error enum prints <code>TooLong(11)</code> instead of a sentence. What was used to print it?Reveal
{:?}. Printing with {} uses the Display block and its sentence.Your Chatterbox so far: v0.7 (after lesson 7.4)
Download this version (zip)Unzip, then cargo run inside the folder.
That is lesson 7.5. Tick it off so the course remembers where you are. That is also the end of chapter 7: 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 Result and the ? operator in Rust?
Result is the type a failing operation returns: Ok with a value or Err with a reason. The ? operator is a shortcut for using one: it takes the value out of an Ok, or returns the Err from the current function.
Why does ? give error E0277 in my function?
The ? operator can only be used in a function that returns a Result or an Option, because on an Err it has to return something. Change the function to return a Result, or handle the error with match instead.
Do Rust tests run when I use cargo run?
No. Tests inside a module marked cfg(test) are only built and run by cargo test. cargo run builds the program without them.
How do I check which error a Rust function returned in a test?
Use assert! with matches! and a pattern such as Err(CountError::TooMany(99)), or take the reason with unwrap_err and compare its to_string with the sentence you expect.
