Enum and match typing practice
A match arm is not a switch case. It is a pattern, a fat arrow, an expression and a comma — and because the whole thing is an expression rather than a statement, the comma at the end is mandatory in a place your fingers expect a semicolon or nothing at all.
The rhythm of an arm
Every match arm follows the same four-beat pattern: pattern, =>, expression, ,. Once that rhythm is in your hands you stop thinking about the syntax and start thinking about the cases, which is the entire point.
The fat arrow is the sticking point. It is two shifted characters in sequence — = then > — and it sits at the boundary between reading a pattern and writing a result, which is exactly where your attention is already switching contexts. That combination of a physically awkward digraph and a cognitive handover is why => is the single most-reported weak token in Rust practice.
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}Tokens in this family
| Token | Reads as | Why it is awkward |
|---|---|---|
=> | arm separator | Two shifted characters in a row, at the moment your attention switches from pattern to result. |
:: | path separator | Doubled punctuation with no space. Typing one colon compiles in some positions, which hides the error. |
match | begin matching | Five common letters, but it must be followed by an expression with no parentheses — unlike `if` in most languages people arrive from. |
_ | catch-all | A shifted character used as an identifier. Easy to omit, and omitting it fails to compile only sometimes. |
if let | match one case | Two keywords with a space, where the second is the one doing the binding. |
while let | loop until no match | Same shape as `if let`, different control flow. Confusing them is a logic bug, not a syntax error. |
@ | bind and test | Rare, and on a key most developers only use for email addresses. |
The five pattern forms
Exhaustiveness is what makes Rust matching worth the keystrokes: the compiler refuses to build if you have not handled every case. These five forms cover almost everything you will write.
fn classify(value: Option<i32>) -> &'static str {
match value {
Some(n) if n < 0 => "negative",
Some(0) => "zero",
Some(n) if n % 2 == 0 => "even",
Some(_) => "odd",
None => "missing",
}
}fn quadrant(point: (i32, i32)) -> &'static str {
match point {
(0, 0) => "origin",
(x, 0) if x > 0 => "positive x axis",
(0, _) => "y axis",
_ => "elsewhere",
}
}
let Point { x, y } = point;match message {
Message::Hello { id: id @ 1..=5 } => format!("small id {}", id),
Message::Hello { id } => format!("other id {}", id),
}
while let Some(top) = stack.pop() {
total += top;
}let else, the newest shape
let ... else binds on the happy path and requires the else branch to diverge — return, break, continue or panic. It replaces a very common nested if let pyramid, and it is worth drilling separately because the block ends with a semicolon after the closing brace, which almost nothing else in Rust does.
fn parse_port(raw: Option<&str>) -> u16 {
let Some(value) = raw else {
return 8080;
};
let Ok(port) = value.parse::<u16>() else {
return 8080;
};
port
}How to practise this
The Enum / Match pack runs from a plain enum through guards, destructuring, binding and let else. Start in copy mode, then switch to gap mode — it blanks the arrows and the path separators specifically, which forces you to reconstruct the arm shape rather than trace it.
If => keeps showing up in your weak-token list after a few sessions, use the retry button on the result screen. It generates a drill aimed at the whole match family rather than a single token, because =>, match, if let and while let are one lesson, not four.
Frequently asked questions
- Why does Rust use => instead of : in match arms?
- Because a match arm produces a value rather than executing a labelled block. The fat arrow reads as 'this pattern yields this expression', which is why arms are separated by commas like list elements rather than terminated by semicolons like statements. A colon would suggest a label, and Rust already uses the colon for type annotations.
- What does exhaustive matching mean?
- The compiler checks that your patterns cover every possible value of the type being matched, and refuses to compile if any case is unhandled. Adding a variant to an enum therefore produces errors at every match that needs updating, rather than a silent fallthrough at runtime. The _ pattern opts out of this check, so use it deliberately.
- When should I use if let instead of match?
- When you care about exactly one pattern and want to ignore the rest. `if let Some(x) = value` is shorter than a match with a None arm that does nothing. Use match when there are two or more cases you actually handle, because the exhaustiveness check is doing work for you there.
- What is the difference between if let and while let?
- if let tests the pattern once. while let keeps re-testing it and loops as long as it matches, which makes it the standard way to drain a stack or a channel: `while let Some(top) = stack.pop()` ends when pop returns None. They look nearly identical, so confusing them produces a hang or a skipped body rather than a compile error.
Keep reading
- Option and Result practice
The two enums you match on most. Their combinators are shorthand for the arms here.
- Borrowing and ownership practice
Patterns bind by reference too — where `ref` and `&` show up inside a match.
- What SyntaxGym measures
Why => shows up in the hesitation report even when you never mistype it.
Try it on a real snippet
No account, nothing uploaded. Your results stay in this browser.