Match in Rust: pattern matching explained
How Rust's match works: literals, ranges, | alternatives, guards, @ bindings, destructuring tuples, structs and enums, and exhaustiveness.
match compares a value against a list of patterns, top to bottom, and runs the first arm that fits. It's an expression, so it produces a value, and it's exhaustive: every possible value must be covered.
fn describe(code: u16) -> &'static str {
match code {
200 => "ok",
301 | 302 => "redirect",
400..=499 => "client error",
500..=599 => "server error",
_ => "unknown",
}
}
fn main() {
for code in [200, 302, 404, 503, 42] {
println!("{code}: {}", describe(code));
}
}
200: ok
302: redirect
404: client error
503: server error
42: unknown
Pattern cheat sheet
| Pattern | Matches |
|---|---|
5, 'x', "quit", true |
that exact literal |
1..=9, 'a'..='z' |
an inclusive range |
A | B |
either pattern |
n (a name) |
anything, and binds it to n |
_ |
anything, binds nothing |
(x, 0) |
a tuple whose second item is 0 |
Point { x, y: 0 } |
a struct, binding x, requiring y == 0 |
Some(v), Err(e) |
an enum variant, binding its data |
[first, .., last] |
a slice with at least two elements |
n @ 1..=9 |
a range, also binding the value to n |
x if x > 100 |
anything, but only if the guard is true |
Destructuring
Patterns take values apart. This works in match, if let, let and function parameters:
match (dx, dy) {
(0, 0) => "standing still",
(0, _) => "vertical",
(_, 0) => "horizontal",
_ => "diagonal",
}
.. skips the remaining fields of a struct (Player { name, .. }) or items of a tuple or slice.
Exhaustiveness
enum Direction {
North,
East,
South,
West,
}
fn turn(d: Direction) -> &'static str {
match d {
Direction::North => "up",
Direction::East => "right",
Direction::South => "down",
}
}
fn main() {
println!("{}", turn(Direction::West));
}
error[E0004]: non-exhaustive patterns: `Direction::West` not covered
For integers and strings you can't list every value, so finish with _ => (or a name to use the value). For your own enums, prefer listing every variant so that adding one later points you at every match to update.
match vs if let
When you only care about one pattern, if let is shorter: if let Some(price) = lookup(item) { ... }. Its sibling let ... else handles the "bail out if it doesn't match" case: let Some(user) = find(id) else { return; };. And matches!(value, pattern) gives a bool.
Common mistakes
Matching against a variable. A plain name in a pattern doesn't compare, it binds a new variable that matches everything:
let target = 5; match guess { target => println!("always runs!"), // shadows `target` }rustc warns "unreachable pattern" for the arms after it. Use a guard:
g if g == target => ....Arms with different types. Every arm must produce the same type: E0308, "
matcharms have incompatible types".Overlapping ranges in the wrong order: the first matching arm wins, so put specific patterns before general ones.
Example
#[derive(Debug)]
enum Event {
Click { x: i32, y: i32 },
Key(char),
Scroll(i32),
Resize(u32, u32),
}
fn handle(e: &Event) -> String {
match e {
Event::Click { x: 0, y: 0 } => String::from("click at the origin"),
Event::Click { x, y } if *x < 0 || *y < 0 => format!("click off screen at ({x}, {y})"),
Event::Click { x, .. } => format!("click in column {x}"),
Event::Key(c @ ('q' | 'Q')) => format!("quit key {c:?}"),
Event::Key(c) if c.is_ascii_digit() => format!("digit {c}"),
Event::Key(c) => format!("key {c}"),
Event::Scroll(0) => String::from("no scroll"),
Event::Scroll(n @ 1..) => format!("scroll down {n}"),
Event::Scroll(n) => format!("scroll up {}", -n),
Event::Resize(w, h) => format!("resize to {w}x{h}"),
}
}
fn main() {
let events = [
Event::Click { x: 0, y: 0 },
Event::Click { x: -5, y: 3 },
Event::Click { x: 40, y: 12 },
Event::Key('Q'),
Event::Key('7'),
Event::Key('a'),
Event::Scroll(3),
Event::Scroll(-2),
Event::Resize(800, 600),
];
for e in &events {
println!("{:<28} -> {}", format!("{e:?}"), handle(e));
}
let scores = [72, 95, 88];
match scores {
[first, .., last] => println!("first {first}, last {last}"),
}
}
Output:
Click { x: 0, y: 0 } -> click at the origin
Click { x: -5, y: 3 } -> click off screen at (-5, 3)
Click { x: 40, y: 12 } -> click in column 40
Key('Q') -> quit key 'Q'
Key('7') -> digit 7
Key('a') -> key a
Scroll(3) -> scroll down 3
Scroll(-2) -> scroll up 2
Resize(800, 600) -> resize to 800x600
first 72, last 88