Lesson 7.1 · 8 min
Operations that can fail: Result
Someone types "/last two" instead of "/last 2". Chatterbox cannot turn "two" into a number, and it must say so instead of crashing. Rust makes "this might fail" part of the answer itself.
Adds to ChatterboxParse a command safely.
An answer or a reason
Chatterbox is about to get a /last 3 command: show the last three messages. The "3" arrives as text, so the program has to turn text into a number. That can fail, because the person might type /last two or /last -1 or /last and nothing else.
Some languages handle this by throwing an exception that flies up the program until something catches it. Rust has no exceptions. Instead, a function that can fail returns a value of a type called Result. It is an enum (lesson 5.3) with exactly two variants:
Ok(value): it worked, and here is the value.Err(reason): it did not work, and here is why.
The type is written Result<T, E>: T is the kind of value you get on success, E is the kind of reason you get on failure. Turning text into a u32 returns Result<u32, ParseIntError>.
$ cargo run
Ok(3)
Err(ParseIntError { kind: InvalidDigit })One new piece of notation: {:?} inside println! prints a value in its debug form, the form meant for programmers, which shows Ok(3) or Err(...) with the variant name. Plain {} is for text meant for people. Standard library types such as Result already know how to print themselves this way.
What does this print?
truefalse-1
Reveal the answer
The program prints: false A u32 cannot be negative, so "-1" is not a valid u32 and you get an Err. is_ok() asks "is it the Ok variant?" and the answer is no.
You cannot ignore it
Here is the part that makes Rust different. A Result is not a number. You cannot add to it or print it as a count. To get at the value inside you must say what to do in both cases, and the tool for that is match from lesson 3.2. Try to cover only the happy case and the compiler stops you.
error[E0004]: non-exhaustive patterns: `Err(_)` not covered --> src/main.rs:3:11 | 3 | match r { | ^ pattern `Err(_)` not covered | note: `Result<u32, ParseIntError>` defined here --> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/result.rs:558:0 ::: /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/result.rs:567:4 | = note: not covered = note: the matched value is of type `Result<u32, ParseIntError>` help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | 4 ~ Ok(n) => println!("count is {n}"), 5 ~ Err(_) => todo!(), |
The message says a pattern is not covered: Err(_). That is the whole safety idea. A failure that nobody wrote code for is a compile error, not a surprise at 2 a.m.
Chatterbox gets /last
Here is the new function in context. parse_count trims spaces and parses. The loop tries two arguments, one good and one bad, and match handles both. (saturating_sub is subtraction that stops at zero, so asking for ten messages when there are three does not underflow. n as usize converts the u32 count into a usize, the type of a list length; as changes one number type into another.) The Message is the same one from chapter 6, with an author and a text.
$ cargo run
/last 2: showing 2
bot: hello!
sam: what is a token?
/last two: invalid digit found in stringWrite count_or_default. It parses arg as a u32 and returns it. If parsing fails it returns default instead. Use match on the Result.
Write count_or_default. It parses arg as a u32 and returns it. If parsing fails it returns default instead. Use match on the Result.
Hidden tests (4): good_number bad_text_uses_default negative_uses_default spaces_are_fine
This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.
What you have now
Chatterbox can read a count from the user and survive a bad one. The failure is a value you can match on, print, or turn into a friendlier message. Coming up, as a preview: the very next lesson shows how to pass a failure up to the caller in a single character, so you do not write a match at every step.
Say it out loud, then reveal
What are the two variants of Result<T, E>?Reveal
Ok(T) for success carrying the value, and Err(E) for failure carrying the reason.Why does Rust refuse a match that only covers Ok?Reveal
Does Rust throw exceptions?Reveal
That is lesson 7.1. Tick it off so the course remembers where you are.