# 7.3 Introduction to match > Rust's match expression: arms, patterns, the _ wildcard, or-patterns, and exhaustiveness checking as a feature. Source: https://learnrust.net/chapter-7/introduction-to-match/ Here's a function that names a digit, written with the tools you have so far: ```rust fn print_digit_name(n: u32) { if n == 1 { println!("one"); } else if n == 2 { println!("two"); } else if n == 3 { println!("three"); } else { println!("something else"); } } fn main() { print_digit_name(2); } ``` ``` two ``` It works, but look at it: the same `n ==` ritual on every line, with the interesting parts (which value, which response) buried in boilerplate. Testing one value against a series of possibilities is so common that Rust has a purpose-built expression for it. ## match Here's the same function, rewritten: ```rust fn print_digit_name(n: u32) { match n { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), _ => println!("something else"), } } fn main() { print_digit_name(2); } ``` ``` two ``` A **match** takes a value to examine, called the **scrutinee** (`n` here, the thing being scrutinized), and a list of **arms** between the braces. Each arm has the form `pattern => expression`. The scrutinee is evaluated once, compared against each arm's **pattern** from top to bottom, and the first pattern that fits wins: its expression runs, and the match is over. No other arm is checked, and there's no way to "fall into" the next arm. The patterns above are the simplest kind, literal values. The last arm's `_` is the **wildcard pattern**: it matches anything, which makes it the "everything else" arm. Since arms are tried in order, the wildcard goes last; anything after it could never win. If you're coming from a C-family language, this is the slot `switch` occupies, and the differences are all in match's favor. A `switch` only works on integer-like types; `match` works on almost anything, including strings and the structured types of chapters 10 and 11. A `switch` falls through from one case to the next unless you remember a `break` on every case, a default so treacherous that compilers warn about it; match arms are sealed rooms, no `break` required. And `switch` will happily let you forget a case. Which brings us to match's best feature. ## Forgetting a case is a compile error Delete the wildcard arm and try to compile: ```rust fn print_digit_name(n: u32) { match n { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), } } fn main() { print_digit_name(2); } ``` ``` error[E0004]: non-exhaustive patterns: `0_u32` and `4_u32..=u32::MAX` not covered --> src/main.rs:2:11 | 2 | match n { | ^ patterns `0_u32` and `4_u32..=u32::MAX` not covered | = note: the matched value is of type `u32` help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms | 5 ~ 3 => println!("three"), 6 ~ 0_u32 | 4_u32..=u32::MAX => todo!(), | ``` Read it with your practiced eye. A match must be **exhaustive**: its arms must cover every value the scrutinee's type can hold. Ours covers 1, 2, and 3, and the compiler computed precisely what's missing: zero, and everything from 4 up. It even drafted the fix. New programmers sometimes read this as the compiler being fussy. It's the opposite: this is the compiler agreeing to remember your edge cases so you don't have to. Every `if`/`else if` chain silently hopes you thought of everything; a match *proves* you did, and re-proves it every compile, forever. When chapter 11 adds types with a fixed set of named cases, exhaustiveness becomes the feature you'd defend with a pitchfork: add a new case to the type, and the compiler lists every match in the program that needs updating. {% callout(kind="note", title="Key insight") %} An `if` chain checks the cases you thought of. A `match` also checks the completeness of your thinking. {% end %} ## One arm, several patterns Patterns can be combined with `|`, read "or": ```rust fn is_vowel(c: char) -> bool { match c { 'a' | 'e' | 'i' | 'o' | 'u' => true, _ => false, } } fn main() { println!("{}", is_vowel('e')); println!("{}", is_vowel('k')); } ``` ``` true false ``` (C's `switch` fakes this by stacking case labels and exploiting fallthrough, the same mechanism that causes its bugs. Here it's just syntax.) ## match is an expression You saw this coming. Like `if`, like blocks, like everything in this language, a match produces a value: the value of whichever arm ran. `is_vowel` above already leans on this (the match is the function's tail expression). It works in `let` position too: ```rust fn main() { let lane = 3; let speed_limit = match lane { 1 => 60, 2 => 80, _ => 100, }; println!("lane {lane}: limit {speed_limit}"); } ``` ``` lane 3: limit 100 ``` And the same rule from lesson [4.7](@/chapter-4/introduction-to-if-expressions.md) follows: in value position, every arm must produce the same type. Make one arm a `&str` and another an integer, and you'll get the familiar E0308, pointing at the odd arm out. ## What we're saving for later Patterns are a much bigger country than literals and `_`. They can capture parts of a value into variables, take apart tuples and structs, and carry extra `if` conditions. All of that arrives in chapter 11, where match meets the types it was born for (and where `Ordering`, a type you'll use in this chapter's project on faith, finally makes full sense). {% callout(kind="note", title="Key insight") %} For now, the working rule: reach for `match` when you're asking "which of these specific values is it?", and for `if` when you're asking about ranges and arbitrary conditions. Lesson 7.10 uses both, each where it's strongest. {% end %} ## Quiz time **Question #1** What does this print? ```rust fn main() { let n = 7; let label = match n % 3 { 0 => "fizz", 1 => "one over", _ => "two over", }; println!("{label}"); } ```
Show solution ``` one over ``` `7 % 3` is 1, which matches the literal pattern `1`. The match is an expression, so `label` receives `"one over"`. Note the arms agree on type (all `&str`), as they must.
**Question #2** Predict the compiler's reaction, precisely: ```rust fn main() { let coin = 'q'; match coin { 'h' => println!("heads"), 't' => println!("tails"), } } ```
Show solution E0004: non-exhaustive patterns. A `char` can be far more than `'h'` and `'t'`, and a match must cover every possibility. The fix is a final arm, for example `_ => println!("not a coin")`. (Whether `'q'` would have matched at runtime is irrelevant; exhaustiveness is checked at compile time, against the type.)
**Question #3** Write a function `calculate(x: i32, y: i32, op: char)` that prints the result of applying `op` to `x` and `y`, supporting `+`, `-`, `*`, `/`, and `%` (the remainder operator from lesson 6.2). For any other `op`, print a complaint. Then call it a few times from `main`.
Show solution ```rust fn calculate(x: i32, y: i32, op: char) { match op { '+' => println!("{}", x + y), '-' => println!("{}", x - y), '*' => println!("{}", x * y), '/' => println!("{}", x / y), '%' => println!("{}", x % y), _ => println!("calculate: unsupported operator '{op}'"), } } fn main() { calculate(7, 2, '+'); calculate(7, 2, '/'); calculate(7, 2, '%'); calculate(7, 2, '?'); } ``` ``` 9 3 1 calculate: unsupported operator '?' ``` The wildcard arm earns its keep: without it, this match would be rejected as non-exhaustive (a `char` has over a million possible values, and we listed five).
Next: programs that refuse to stop. Loops, including the one C-family languages don't have.