Skip to content

← Shape Your Data: Structs, Enums, Pattern Matching step 7 of 26

Easy Primitives

match and exhaustiveness: the compiler as a checklist

Classify spreadsheet cell contents. Each input string becomes one of five Cell variants, and each variant renders to a label.

pub fn classify(inputs: Vec<String>) -> Vec<String>
input variant label
"" Empty empty
42 Number(42) number(42)
true / false Flag(bool) flag(true)
=A1+B1 Formula("A1+B1") formula(A1+B1)
anything else Text(String) text(5) — the count of chars

The parser is given. You write label, as one exhaustive match with no wildcard arm.

Exhaustiveness is the payoff for sum types

A match on an enum must cover every variant. Miss one and the program does not compile:

error[E0004]: non-exhaustive patterns: `Cell::Formula(_)` not covered

Read what that buys you. Add a sixth variant to Cell tomorrow — say Error(String) — and rustc will walk you to every match in the codebase that needs updating and name the missing case at each one. Not a runtime surprise, not a test that happened to cover it, not a default: branch that silently did the wrong thing. A list of edits.

Most languages the audience knows have no equivalent. A switch over strings, a chain of instanceof, a visitor interface — all of them let you add a case and ship the gap. This is the single feature most likely to convert a Rust sceptic, and it only works if you do not defeat it. (The next item is entirely about the way people defeat it.)

match is an expression

Every arm produces a value, and all arms must produce the same type (E0308 if they do not). So a match is something you can assign, return, or pass directly:

let label = match cell {
    Cell::Empty => "empty".to_string(),
    Cell::Number(number) => format!("number({number})"),
    // ...
};

Note Cell::Number(number) — the pattern both tests the variant and extracts its payload in one move. There is no separate “cast to the right type” step, because the pattern already proved which variant it is.

The nastiest bug in this cluster

Write Empty => instead of Cell::Empty => and something quietly different happens: a lowercase-or-not bare identifier in a pattern is a new binding, not a variant test. It matches everything and shadows the name. Your first arm becomes a catch-all and the other four are dead.

rustc catches this specific case because the name collides with a real variant:

error[E0170]: pattern binding `Empty` is named the same as one of the
              variants of the type `Cell`

The starter ships that error — meet it once on purpose. But notice how narrow the rescue is. Mistype it as Emtpy and there is no collision, so there is no E0170. You get a silent catch-all, an unreachable_pattern warning on the arms below it, and a wrong program. The habit worth building: always write the enum name in the pattern.

Lints in this neighbourhood

single_match pushes a two-arm match where one arm is {} toward if let. match_like_matches_macro pushes an arm-per-boolean match toward matches!. match_single_binding complains about a match whose only arm binds a name — that is a let. All three are on by default. They are worth reading as one message: match is for choosing between shapes; when you are not choosing, something shorter says it better.

Remember the grade is compile + tests + clippy -D warnings.

Loading visualization…