Pattern Matching
Pattern matching lets Rust compare a value against a pattern, extract data from it, and decide what to do safely and exhaustively.
Think of it as:
A supercharged
switchstatement that understands structure, ownership, and types.
let number = 3;
match number {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Something else"),
}
- Rust checks each arm top to bottom
- The first matching pattern runs
_is a catch-allmatchmust be exhaustive
Why Pattern Matching Matters
Rust data structures are often shaped, not flat: Tuples, Structs, Enums (Option, Result), Slices
Pattern matching lets you look inside these shapes safely.
Matching Enums (Most Important Use Case)
Example: Enum with Data
enum Message {
Quit,
Write(String),
Move { x: i32, y: i32 },
}
Matching Variants
fn handle_message(msg: Message) {
match msg {
Message::Quit => println!("Quit"),
Message::Write(text) => println!("Text: {}", text),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
}
}
Why this is powerful:
- Rust forces you to handle every variant
- Data is safely extracted
- No null checks, no invalid states
Pattern Matching with Option<T>
Definition Reminder
enum Option<T> {
Some(T),
None,
}
Example
let name = Some("Rust");
match name {
Some(value) => println!("Name: {}", value),
None => println!("No name provided"),
}
Rust won’t let you “forget” the None case. That’s deliberate.
Pattern Matching with Result<T, E>
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
match divide(10, 2) {
Ok(value) => println!("Result: {}", value),
Err(err) => println!("Error: {}", err),
}
Pattern matching turns error handling into explicit logic, not hope.
Matching Tuples
let point = (0, 5);
match point {
(0, y) => println!("On Y axis at {}", y),
(x, 0) => println!("On X axis at {}", x),
(x, y) => println!("Point at ({}, {})", x, y),
}
Patterns describe structure, not just values.
Matching Structs
struct User {
name: String,
age: u32,
}
let user = User {
name: String::from("Alice"),
age: 20,
};
Destructuring in match
match user {
User { name, age: 18 } => println!("Just became an adult: {}", name),
User { name, age } => println!("{} is {} years old", name, age),
}
Field names become variables automatically.
Ignoring Values with _ and ..
Ignore Single Values
let (_, y) = (10, 20);
Ignore Remaining Fields
match user {
User { name, .. } => println!("User: {}", name),
}
This keeps patterns readable and future-proof.
Match Guards (if Conditions)
Patterns can have extra conditions.
let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("Even number"),
Some(_) => println!("Odd number"),
None => println!("No number"),
}
Guards refine patterns without nesting logic.
if let – Pattern Matching Lite
Use if let when you care about one pattern only.
let msg = Message::Write(String::from("Hello"));
if let Message::Write(text) = msg {
println!("Message: {}", text);
}
Less verbose, less exhaustive—use wisely.
while let – Pattern Matching in Loops
let mut stack = vec![1, 2, 3];
while let Some(value) = stack.pop() {
println!("{}", value);
}
The loop ends naturally when the pattern no longer matches.
Matching Slices and Arrays
let numbers = [1, 2, 3];
match numbers {
[1, _, 3] => println!("Matched pattern"),
_ => println!("No match"),
}
Variable-Length Slice Pattern
match &numbers[..] {
[first, middle @ .., last] =>
println!("First: {}, Last: {}", first, last),
}
This is structural reasoning, not indexing.
Ownership and Pattern Matching
Patterns can move, borrow, or copy values.
let msg = Message::Write(String::from("Hello"));
match msg {
Message::Write(text) => println!("{}", text), // moved
_ => (),
}
To avoid moving:
match &msg {
Message::Write(text) => println!("{}", text),
_ => (),
}
Summary
- Pattern matching inspects structure, not just values
matchmust be exhaustive- Works deeply with enums, structs, tuples, slices
- Enforces correctness at compile time
- Replaces null checks and fragile branching