Lesson 5.5 · 13 min
Matching the shape of a value
Type /h to Chatterbox and it says it does not know that command. Everyone types the short form sooner or later. Fixing it takes one character, |, and it opens the door to everything else a match arm can say.
Adds to ChatterboxShort aliases like /h and /q, and /debug to see how a line was understood.
Compiler errors you will meet E0004
One arm, several words
Every arm of a match starts with a pattern: a description of a shape that a value may or may not have. "/help" is a pattern that fits exactly one piece of text. Command::Say(text) is a pattern that fits any Say and gives its contents a name. Rust checks the arms from top to bottom and runs the first one whose pattern fits.
The bar | joins patterns with "or": "/help" | "/h" fits either word. Both lead to the same code, so the answer is written once and the two words can never drift apart.
What does this program print?
help quit unknownhelp quit helphelp unknown unknown
Reveal the answer
The program prints: help quit unknown Each side of | is a whole pattern. "/h" fits the first arm and "/exit" the second. "/hq" is neither "/h" nor "/q": a text pattern fits only exactly that text, so it falls to _.
Ranges and guards
A pattern can also be a range. 1..=9 fits any number from 1 to 9, both ends included (the = is what includes the 9). It works for numbers and for single characters, such as 'a'..='z' (single quotes hold exactly one character; double quotes hold text).
When the shape is not enough, add a guard: if and a question after the pattern. n if n >= 100 fits any number, names it n, and then only counts as a fit when the question is true. If the guard says no, Rust moves on to the next arm as if this one had not fitted at all.
What does this program print?
warming upchattya whole novel
Reveal the answer
The program prints: chatty 10 is not 0 and not in 1..=9, which stops at 9. The third pattern, n, fits any number, but its guard asks whether 10 is at least 100; it is not, so that arm is skipped and _ takes it.
Taking a value apart
A pattern can be shaped like a struct, a pair or a variant, and then it does more than check: it takes the value apart and gives each piece its own name. This works in a let too, not only in a match. Watch what happens to the keys when a Message is taken apart.
Step through the program. Solid arrows are ownership; dashed arrows are borrows. Use ← → or the slider.
- Line 6. The program starts in main.
- Line 7. A Message is built. Its two fields each own a String in the heap: m.author owns "you" and m.text owns "hi".
- Line 11. The pattern has the shape of a Message, with a name where each field goes. The author key moves to a new owner called author. No text is copied.
- Line 11. The same line moves the text key to text. Both fields have moved out, so m can no longer be used as a whole.
- Line 12. The two new names are ordinary owners and print like any String. stdout:
you said hi - Line 13. main ends. author and text are dropped, and their heap blocks are freed.
Taking apart is moving, by the rules of chapter 4: each field's key goes to its new name. When you only want to look, take apart a borrow instead, as Chatterbox does with &last: then the names inside are borrows and the original stays whole. Taking a pair apart is the same idea: let (major, minor) = VERSION; gives the two numbers of lesson 2.6 a name each.
Two marks say "I do not need this part". _ skips exactly one piece and binds nothing, so nothing moves: Command::Say(_) fits any Say without touching its text. .. skips all the rest: Message { author, .. } takes only the author.
if let and while let
Sometimes only one shape matters. if let Some(text) = &last { … } else { … } is a match with one interesting arm: run the block if the value fits, with text named inside; otherwise run the else. while let is the same question asked again and again, and the loop stops the first time the value does not fit. Here it peels a sentence apart one word at a time:
$ cargo run
peeled off: rust
peeled off: is
last word: fun, 3 wordsEvery shape, or the compiler says so
You met E0004 in lessons 3.2 and 5.3: a match has to cover every value. Patterns that look inside a value make that check sharper, because covering a variant now means covering every shape inside it too. Here Say carries an Option, and only one of its two shapes has an arm:
This is the exact output of the real compiler. Toggle the English, then pick a fix and see what actually happens.
error[E0004]: non-exhaustive patterns: `Command::Say(None)` not covered --> src/main.rs:7:11 | 7 | match cmd { | ^^^ pattern `Command::Say(None)` not covered | note: `Command` defined here --> src/main.rs:1:6 | 1 | enum Command { | ^^^^^^^ 2 | Help, 3 | Say(Option<String>), | --- not covered = note: the matched value is of type `Command` help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | 9 ~ Command::Say(Some(text)) => text, 10 ~ Command::Say(None) => todo!(), |
E0004 in the compiler error atlas →
non-exhaustive patterns: `Command::Say(None)` not covered: there is an arm for a Say with text in it, and none for a Say with nothing. The compiler names the exact shape you forgot.- The pointer under
match cmdshows which match is incomplete. In a big program that matters: it is the one that will refuse to build. - The note about
Commandshows where the enum is defined, so you can see every variant it could be. help: ensure that all possible cases are being handled: the compiler even writes the missing arm for you to fill in.
Fixes that were compiled for real
- Add the missing shape — compiles. One more arm for a bare Say. Every shape now has its own answer, and if someone adds a variant later, this match will tell them.
- Trap: join the two shapes with | — still E0408. An arm runs the same code for every pattern in it, so every pattern must give the same names. A bare Say has no text to call <code>text</code>, and the compiler refuses.
- Catch everything else with _ — compiles, with a cost. It compiles, and today it even behaves. The cost comes later: when a new variant is added, <code>_</code> swallows it silently, and the compiler can no longer point you at this match.
Seeing how a line was understood
When Chatterbox gives a strange answer, the first question is "what did it think I typed?". That means printing a Command. But {} in println! only knows how to print things that have one obvious look for people, like text and numbers. A type you made has no such look until you write one, and asking for it is an error:
error[E0277]: `Command` doesn't implement `Debug` --> src/main.rs:8:22 | 8 | println!("{:?}", cmd); | ---- ^^^ `Command` cannot be formatted using `{:?}` because it doesn't implement `Debug` | | | required by this formatting parameter | = help: the trait `Debug` is not implemented for `Command` help: consider annotating `Command` with `#[derive(Debug)]` | 1 + #[derive(Debug)] 2 | enum Command { |
The {:?} asks for the debug form: a plain view of a value's shape, meant for the programmer. Command has none, so the compiler says it "doesn't implement Debug". You do not have to write that printing code by hand. Put #[derive(Debug)] on the line above the enum, and the compiler writes it for you: variant name, then whatever is inside, in brackets. The same works for structs. With it, that program prints Say(Some("hi")).
Chatterbox v0.5: short forms and /debug
Here is the whole program after this lesson, new lines in green.
- In
parse, two arms grow alternatives:"/help" | "/h"means help, and"/quit" | "/q" | "/exit"means quit. The help text lists them too: it now ends/quit (or /h, /q, /exit), after a new/debug <text>. Commandgets#[derive(Debug)]and a new variant,Debug(Option<String>), built withargandownedexactly likeSay.- In
main,Command::Debug(Some(text))parsestextas if you had typed it, and prints the result with{:?}. So/debug /say hishowsSay(Some("hi")), and/debug helloshowsChat("hello"). (That first output will change in chapter 7, whenparselearns to report errors: it becomesOk(Say("hi")).) A bare/debuggets its own arm, soE0004stays quiet. /againis now anif let Some(text) = &lastwith anelse. It does exactly what thematchdid; the shape you care about simply comes first.
Sam /h /q after the quit
Chatterbox v0.5 What should I call you? Hi Sam! I'm Chatterbox. Ask me anything. bot: commands: /help, /again, /history, /say <text>, /shout <text>, /debug <text>, /quit (or /h, /q, /exit) bot: bye, Sam! messages so far: 0
Show the whole program (open it after you have tried the exercise below: it contains the answer)
Download this version (zip)Unzip, then cargo run inside the folder.
And here is /debug showing how two lines were understood. The quotes and brackets are the derived debug form, straight from the compiler's own printing code.
Sam /debug /say hi /debug hello /quit
Chatterbox v0.5
What should I call you?
Hi Sam! I'm Chatterbox. Ask me anything.
bot: Say(Some("hi"))
bot: Chat("hello")
bot: bye, Sam!
messages so far: 0This is the whole Chatterbox program with the short forms taken out of parse: only /help and /quit work. Put them back, so that /h means help and both /q and /exit mean quit. A short form must be the whole word: /hq is still an unknown command.
This is the whole Chatterbox program with the short forms taken out of parse: only /help and /quit work. Put them back, so that /h means help and both /q and /exit mean quit. A short form must be the whole word: /hq is still an unknown command.
Hidden tests (4): a_short_form_is_a_whole_word debug_shows_the_parsed_shape quit_and_both_short_forms help_and_its_short_form
This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.
Say it out loud, then reveal
What does <code>"/quit" | "/q"</code> mean as a pattern?Reveal
What happens when an arm's guard says no?Reveal
Why did joining <code>Say(Some(text)) | Say(None)</code> in one arm fail?Reveal
text. That is E0408.What does <code>#[derive(Debug)]</code> do?Reveal
{:?} works on your own type.That is lesson 5.5. Tick it off so the course remembers where you are.
Go deeper
Questions people ask
What is a pattern in Rust?
A pattern describes a shape a value may have, such as an exact piece of text, a range of numbers or an enum variant with something inside. A match runs the first arm whose pattern fits, and a pattern can give names to the parts it takes out.
How do I match several values in one Rust match arm?
Join them with a bar, as in "/quit" | "/q" | "/exit". Every pattern in the arm must give the same names.
What is the difference between _ and .. in a pattern?
An underscore skips exactly one piece and binds nothing. Two dots skip all the remaining pieces, for example every field of a struct you did not name.
What is the difference between if let and match?
if let checks a single pattern and runs its block only when the value fits, with an optional else. match lists arms for every possible shape, and the compiler checks that none is missing.
Why does println with {:?} fail on my own type?
Your type has no debug form yet. Add #[derive(Debug)] above the struct or enum, and the compiler writes the printing code for you.