13. Pattern Matching (match · if let · while let)
Rust's pattern matching is a different beast from switch in other languages — it does value matching plus destructuring plus guards plus ref/mut binding in one form. This lesson covers match arm patterns, guards, ranges, OR patterns, and the shorter `if let` / `while let`.
What you'll learn
- 1Distinguish literal / variable / wildcard / range / OR patterns
- 2Destructure tuples / structs / enums via patterns
- 3Add `if` guards to match arms
- 4Use `if let` for single-variant short forms
- 5Use `while let` to iterate while an iterator yields Some
Overview
Pattern matching keeps Rust code short and safe. Splitting a value into cases inside a single expression cuts temporary variables and clarifies intent at a glance.
Core Concepts
1) Basic match arm patterns
| Pattern | Example | Meaning |
|---|---|---|
| literal | 1, "hi" | exact value |
| variable | x | bind any value to x |
| wildcard | _ | any value, no binding |
| range | 1..=5 | 1 through 5 |
| OR | 1 | 2 | 3 | one of these |
| destructure | Point { x, y } | extract fields |
2) match is an expression with exhaustive checking
It produces a value, and missing a variant is a **non-exhaustive patterns** compile error. You'll get used to that diagnostic.
3) Guards — if on a match arm
match n {
x if x < 0 => println!("negative"),
0 => println!("zero"),
x => println!("positive {}", x),
}4) if let / while let — single-variant focus
Syntactic sugar for the case where only one variant matters.
Hands-on Examples
Tuple / enum destructuring with guards:
fn classify(p: (i32, i32)) -> &'static str {
match p {
(0, 0) => "origin",
(x, 0) if x > 0 => "positive x-axis",
(x, 0) => "negative x-axis",
(0, y) if y > 0 => "positive y-axis",
(0, _) => "negative y-axis",
(x, y) if x == y => "on y=x",
_ => "other",
}
}
fn main() {
for p in [(0,0),(3,0),(0,-2),(5,5),(2,7)] {
println!("{:?} → {}", p, classify(p));
}
}if let — single variant of interest:
let some_val: Option<i32> = Some(7);
if let Some(n) = some_val {
println!("got {}", n);
} else {
println!("none");
}while let — iterate while Some:
fn main() {
let mut stack = vec![1, 2, 3, 4];
while let Some(top) = stack.pop() {
println!("{}", top); // 4, 3, 2, 1
}
}@ binding — check value while capturing:
let n = 42;
match n {
x @ 1..=50 => println!("in 1~50: {}", x),
_ => println!("out of range"),
}Common Mistakes
Q. What's the difference between `_` and a variable name?
A. `_` ignores the value (no binding). A variable name **captures** the value, so you can use it in a guard or the body.
Q. Too many variants — can I just use `_`?
A. You can, but you lose the safety net of having the compiler tell you when a new variant is added. Prefer explicit patterns when feasible.
Q. Does `if let` have an `else`?
A. Yes — `if let Some(x) = opt { ... } else { ... }`. There's also `let-else` for unwrapping into the surrounding flow.
Recap
- match is an expression, exhaustive over variants
- Patterns: literal / variable / wildcard / range / OR / destructure / @-binding
- Guards add `if` conditions to arms
- Use if let / while let / let-else for concise single-case handling
Try It Yourself
- Define `enum Direction { Up, Down, Left, Right }` and return (dx, dy) tuples via match
- Take Vec<Option<i32>> and extract only Some values into a Vec<i32>
- Use @-binding to bucket numbers into 1~9 / 10~99 / 100+ while keeping the original value
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗