Rust Pattern Matching Tutorial
Use Rust's match expression, if-let, while-let, and pattern syntax to destructure data safely with exhaustive compile-time checking.
What you'll learn
- ✓match exhaustiveness
- ✓Pattern syntax
- ✓if-let and while-let
- ✓Guards and bindings
- ✓Refutable vs irrefutable
Prerequisites
- •Basic familiarity with Rust
What and Why
Pattern matching is how you destructure data in Rust: enums, structs, tuples, slices, references, and constants. The match expression must cover every possibility, so the compiler catches forgotten cases at compile time.
That exhaustiveness check is one of Rust’s most valuable safety features. Adding a new variant to an enum forces every match on it to be updated, eliminating an entire class of bugs.
Mental Model
A pattern describes the shape of a value. The compiler verifies that every possible value has at least one matching arm. Patterns can:
- match a literal (
1,"hello") - bind a name (
x,name @ pattern) - destructure (
Some(x),Point { x, y },(a, b, _)) - match by range (
0..=9) - use OR (
1 | 2 | 3) - attach a guard (
x if x > 5)
enum Shape { Circle, Square, Triangle }
|
v
match s { <-- compiler verifies all 3 variants covered
Circle => ...,
Square => ...,
Triangle => ...,
} Hands-on Example
enum Event {
Click { x: i32, y: i32 },
Key(char),
Scroll(f64),
Close,
}
fn handle(e: Event) -> String {
match e {
Event::Click { x, y } if x >= 0 && y >= 0 => format!("click at ({x},{y})"),
Event::Click { .. } => "click off-screen".into(),
Event::Key(c @ 'a'..='z') => format!("lowercase {c}"),
Event::Key(c) => format!("key {c}"),
Event::Scroll(amount) => format!("scroll {amount}"),
Event::Close => "close".into(),
}
}
fn main() {
println!("{}", handle(Event::Click { x: 10, y: 20 }));
println!("{}", handle(Event::Key('z')));
// if-let for one-arm matching
let maybe = Some(42);
if let Some(n) = maybe {
println!("got {n}");
}
// while-let for loops
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
}
The c @ 'a'..='z' syntax binds c while also asserting it’s a lowercase letter.
Common Pitfalls
Catch-all that masks bugs. Writing _ => unreachable!() defeats exhaustiveness. If you add an enum variant later, the compiler won’t warn you. Use it only when truly impossible.
Shadowing instead of comparing. match x { y => ... } binds y to x rather than comparing against an outer y. To match against a named constant, use const Y: i32 = 5; and match x { Y => ... } (uppercase constants are recognized as patterns).
Forgetting refs. When matching on &Some(x), x is the inner value by value. When matching on Some(ref x), x is a reference. Newer Rust uses match ergonomics to insert refs automatically; understand the rule when it surprises you.
Slice patterns are powerful but verbose: match nums.as_slice() { [first, .., last] => ..., [] => ... }. Easy to forget the empty case.
Practical Tips
- Lead with the most specific patterns; the compiler matches top-down.
- Use guards (
if) for conditions on bound values, but keep them simple; complex guards belong in a function. - Prefer
matchover chains ofif letwhen you have several variants. matches!(value, Pattern)is a handy macro that returns abool, perfect forfilterpredicates.- For struct patterns, use
..to ignore remaining fields and stay forward-compatible. let else(Rust 1.65+) handles early-exit destructuring cleanly:
let Some(n) = parse(input) else { return Err("bad"); };
Wrap-up
Pattern matching is one of Rust’s defining features. It makes enums actually usable as the algebraic data types they are, eliminates whole classes of forgotten-case bugs, and reads like prose. Reach for match whenever you have a value with multiple possible shapes; reach for if let or let else for simple one-arm cases. Once it’s part of your reflex set, you’ll wish other languages had it.
Related articles
- Rust Rust Actix vs Axum Comparison
A side-by-side comparison of Actix Web and Axum, covering architecture, ergonomics, performance, ecosystem, and how to pick the right one for your project.
- Rust Rust async/await and Futures Explained
How Rust's async/await desugars into a state machine, what a Future actually is, and the runtime model that makes it efficient on real workloads.
- Rust Rust Axum Web Framework Tutorial
A practical introduction to building HTTP services in Rust using the Axum web framework, with routing, extractors, state, and JSON handling.
- Rust Rust Builder Pattern Explained
Learn the builder pattern in Rust, why it fits the language so well, and how to use it for ergonomic, type-safe configuration of complex structs.