Match Expressions
Rust's powerful match control flow, and why the compiler forces you to handle every possible case.
2 min read
match is Rust's answer to switch, but considerably more powerful: it compares a value against a series of patterns, runs the code for the first one that matches, and — critically — the compiler forces you to cover every possibility.
Basic matching
fn main() {
let number = 3;
match number {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("something else"),
}
}The _ pattern is a catch-all, matching anything not already handled — similar to default in a switch statement. Unlike switch in C-family languages, there's no fallthrough between arms and no break needed.
Exhaustiveness is enforced
Remove the _ arm above and the code won't compile:
match number {
1 => println!("one"),
2 => println!("two"),
// error: non-exhaustive patterns, `_` not covered
}This matters most once you start matching on enums, covered in a later lesson — if you add a new variant to an enum months from now, every match on that enum that doesn't have a catch-all will fail to compile until you handle the new case. That's the compiler doing your regression testing for you.
match as an expression
Like if, match produces a value:
fn main() {
let number = 4;
let description = match number {
1 => "one",
2 => "two",
_ => "many",
};
println!("{description}");
}Matching ranges and multiple values
fn main() {
let grade = 82;
match grade {
90..=100 => println!("A"),
80..=89 => println!("B"),
70..=79 => println!("C"),
_ => println!("F"),
}
let day = 6;
match day {
1 | 7 => println!("weekend"),
2..=6 => println!("weekday"),
_ => println!("invalid"),
}
}..= matches an inclusive range, and | matches any one of several values in a single arm.
Binding values in a pattern
A match arm can bind part of the matched value to a name for use inside that arm:
fn main() {
let pair = (0, -2);
match pair {
(0, y) => println!("on the y-axis at {y}"),
(x, 0) => println!("on the x-axis at {x}"),
(x, y) => println!("at ({x}, {y})"),
}
}This kind of destructuring match is everywhere in idiomatic Rust, especially once you're working with the Option and Result types covered later — match is how you safely pull a value out of them without risking a crash on a case you forgot to handle.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.