Pattern Matching in Rust
Master Rust's pattern matching — match expressions, if let, while let, destructuring, guards, and patterns in function parameters.
What you'll learn
- ✓How match expressions work and why they must be exhaustive
- ✓Destructuring structs, enums, and tuples
- ✓if let and while let for concise matching
- ✓Match guards, bindings, and or-patterns
Prerequisites
- •Rust basics (variables, functions, enums)
- •Understanding of Option and Result types
Pattern matching is Rust’s most expressive control flow tool. The match expression forces you to handle every case, and destructuring lets you pull apart complex data in a single line.
The match expression
fn describe_number(n: i32) -> &'static str {
match n {
0 => "zero",
1 => "one",
2..=9 => "single digit",
10..=99 => "double digit",
_ => "large",
}
}
Every possible value must be covered. The _ wildcard catches everything else. If you miss a case, the compiler tells you.
Matching enums
enum Direction {
North,
South,
East,
West,
}
fn move_player(dir: Direction) {
match dir {
Direction::North => println!("Moving north"),
Direction::South => println!("Moving south"),
Direction::East => println!("Moving east"),
Direction::West => println!("Moving west"),
}
}
Destructuring
Enums with data
enum Message {
Quit,
Echo(String),
Move { x: i32, y: i32 },
Color(u8, u8, u8),
}
fn handle(msg: Message) {
match msg {
Message::Quit => println!("Quitting"),
Message::Echo(text) => println!("Echo: {text}"),
Message::Move { x, y } => println!("Move to ({x}, {y})"),
Message::Color(r, g, b) => println!("Color: #{r:02x}{g:02x}{b:02x}"),
}
}
Structs
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 3, y: 7 };
let Point { x, y } = p;
println!("x={x}, y={y}");
Tuples
let (a, b, c) = (1, "hello", 3.14);
Nested destructuring
let ((a, b), Point { x, y }) = ((1, 2), Point { x: 3, y: 4 });
Option and Result
Pattern matching is the idiomatic way to handle Option and Result.
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some("Alice".to_string())
} else {
None
}
}
match find_user(1) {
Some(name) => println!("Found: {name}"),
None => println!("Not found"),
}
match std::fs::read_to_string("config.toml") {
Ok(contents) => println!("Config: {contents}"),
Err(e) => eprintln!("Error: {e}"),
}
if let
When you only care about one variant, if let is more concise than a full match.
let value: Option<i32> = Some(42);
if let Some(n) = value {
println!("Got: {n}");
}
Equivalent to:
match value {
Some(n) => println!("Got: {n}"),
_ => {}
}
while let
Loop while a pattern matches.
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
// prints 3, 2, 1
Match guards
Add an if condition to a match arm.
let num = Some(4);
match num {
Some(n) if n < 0 => println!("Negative: {n}"),
Some(n) if n == 0 => println!("Zero"),
Some(n) => println!("Positive: {n}"),
None => println!("Nothing"),
}
Or-patterns
Match multiple patterns in one arm with |.
let x = 3;
match x {
1 | 2 => println!("one or two"),
3 | 4 => println!("three or four"),
_ => println!("other"),
}
Bindings with @
Bind a value while also testing it against a pattern.
match age {
n @ 0..=12 => println!("Child aged {n}"),
n @ 13..=17 => println!("Teen aged {n}"),
n @ 18.. => println!("Adult aged {n}"),
}
Ignoring values
Use _ to ignore a single value, .. to ignore remaining fields.
let (first, _, third) = (1, 2, 3); // ignore second
struct Point3D { x: f64, y: f64, z: f64 }
let Point3D { x, .. } = point; // only extract x
Patterns in function parameters
fn print_point(&(x, y): &(i32, i32)) {
println!("({x}, {y})");
}
let point = (3, 5);
print_point(&point);
Summary
Pattern matching in Rust is exhaustive, expressive, and safe. Use match when you need to handle all cases, if let for one-variant checks, and destructuring to pull apart complex data. The compiler enforces that you handle every possibility — missing a case is a compile error, not a runtime bug.
Related articles
- Rust Rust Pattern Matching: Advanced Techniques and Guards
Go beyond basic match with pattern guards, bindings, nested patterns, or-patterns, and real-world refactoring strategies in Rust.
- Rust Error Handling with Result and Option in Rust
Handle errors idiomatically in Rust — Result, Option, the ? operator, custom error types, and the thiserror/anyhow ecosystem.
- Rust Traits and Generics in Rust
Master Rust traits and generics — defining traits, trait bounds, default methods, trait objects, and generic data structures.
- Rust Cargo Basics: Building, Testing, and Publishing Rust Code
A practical tour of Cargo — creating projects, managing dependencies and features, running tests, building release binaries, and using workspaces.