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,
    }
}
Four arms, four commas. The last one is not optional in the way you might expect.

Tokens in this family

Enum and match tokens, and where each goes wrong at the keyboard.
TokenReads asWhy it is awkward
=>arm separatorTwo shifted characters in a row, at the moment your attention switches from pattern to result.
::path separatorDoubled punctuation with no space. Typing one colon compiles in some positions, which hides the error.
matchbegin matchingFive common letters, but it must be followed by an expression with no parentheses — unlike `if` in most languages people arrive from.
_catch-allA shifted character used as an identifier. Easy to omit, and omitting it fails to compile only sometimes.
if letmatch one caseTwo keywords with a space, where the second is the one doing the binding.
while letloop until no matchSame shape as `if let`, different control flow. Confusing them is a logic bug, not a syntax error.
@bind and testRare, and on a key most developers only use for email addresses.
Enum and match tokens, and where each goes wrong at the keyboard.

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",
    }
}
A guard: a pattern plus a condition.
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;
Destructuring a tuple and a struct in the pattern position.
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;
}
Binding with @, and looping with while let.

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
}
Note the `};` — a closing brace followed by a semicolon.

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

Try it on a real snippet

No account, nothing uploaded. Your results stay in this browser.