The match Expression
Rust's match is like a supercharged switch statement. It must be
exhaustive — every possible value must be handled. The compiler
enforces this, which eliminates an entire class of bugs.
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
#[derive(Debug)]
enum UsState {
Alabama, Alaska, Arizona, California,
}
fn value_in_cents(coin: &Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("Quarter from {:?}", state);
25
}
}
}
fn main() {
let coin = Coin::Quarter(UsState::California);
println!("Value: {} cents", value_in_cents(&coin));
}
Matching on Values and Ranges
fn main() {
let number = 13;
match number {
1 => println!("One"),
2 | 3 | 5 | 7 | 11 | 13 => println!("Prime"),
14..=19 => println!("Teen (14-19)"),
_ => println!("Something else"), // _ is the wildcard
}
// Matching with bindings
let msg = match number {
n @ 1..=12 => format!("{n} is small"),
n @ 13..=19 => format!("{n} is a teen"),
n => format!("{n} is big"),
};
println!("{msg}");
// Match is an expression — it returns a value!
let boolean = true;
let binary = match boolean {
true => 1,
false => 0,
};
println!("{binary}");
}
Destructuring in Patterns
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
enum Command {
Quit,
Echo(String),
Move { x: i32, y: i32 },
Color(u8, u8, u8),
}
fn main() {
// Destructuring structs
let p = Point { x: 5, y: 10 };
let Point { x, y } = p;
println!("x={x}, y={y}");
match p {
Point { x: 0, y } => println!("On y-axis at {y}"),
Point { x, y: 0 } => println!("On x-axis at {x}"),
Point { x, y } => println!("({x}, {y})"),
}
// Destructuring enums
let cmd = Command::Move { x: 10, y: 20 };
match cmd {
Command::Quit => println!("Quit"),
Command::Echo(msg) => println!("Echo: {msg}"),
Command::Move { x, y } => println!("Move to ({x}, {y})"),
Command::Color(r, g, b) => println!("Color: ({r}, {g}, {b})"),
}
// Destructuring tuples
let (a, b, c) = (1, "hello", true);
println!("{a} {b} {c}");
// Nested destructuring
let ((feet, inches), name) = ((5, 11), "Alice");
println!("{name} is {feet}'{inches}"");
}
Match Guards and @ Bindings
fn main() {
let num = Some(42);
// Match guard: extra condition with 'if'
match num {
Some(n) if n < 0 => println!("Negative: {n}"),
Some(n) if n == 0 => println!("Zero"),
Some(n) if n > 100 => println!("Big: {n}"),
Some(n) => println!("Normal: {n}"),
None => println!("Nothing"),
}
// @ binding: bind a value while testing a pattern
let age = 25;
match age {
n @ 0..=12 => println!("Child aged {n}"),
n @ 13..=19 => println!("Teenager aged {n}"),
n @ 20..=64 => println!("Adult aged {n}"),
n @ 65.. => println!("Senior aged {n}"),
_ => unreachable!(),
}
// Combining guards and bindings
let x = Some(5);
match x {
Some(n @ 1..=10) if n % 2 == 0 => println!("Even 1-10: {n}"),
Some(n @ 1..=10) => println!("Odd 1-10: {n}"),
Some(n) => println!("Other: {n}"),
None => println!("None"),
}
}
if let and while let
fn main() {
let config_max = Some(3u8);
// Verbose: match with only one interesting case
match config_max {
Some(max) => println!("Max: {max}"),
_ => (),
}
// Concise: if let for single-pattern matching
if let Some(max) = config_max {
println!("Max: {max}");
}
// if let with else
let coin = Coin::Penny;
if let Coin::Quarter(state) = &coin {
println!("Quarter from {:?}", state);
} else {
println!("Not a quarter");
}
// while let: loop while pattern matches
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("Popped: {top}");
}
// Prints: 3, 2, 1
// let else: bind or diverge (Rust 1.65+)
let s = "42";
let Ok(n) = s.parse::() else {
println!("Failed to parse");
return;
};
println!("Parsed: {n}");
}
enum Coin { Penny, Quarter(String) }
Why match is Better Than switch
- Exhaustive: You must handle every possible variant — no silent fallthrough
- Destructuring: Extract data from enums, structs, and tuples inline
- Expression-based: match returns a value you can assign
- No fallthrough: Each arm is independent — no accidental bugs from missing break
- Pattern guards: Add conditions to patterns for precise control
Key Takeaways
- ✅
matchmust be exhaustive — the compiler catches unhandled cases - ✅ Patterns can destructure structs, enums, tuples, and nested structures
- ✅ Use match guards (
if) for additional conditions on patterns - ✅
if letandwhile letsimplify single-pattern matching - ✅
@ bindingslet you capture a value while testing a pattern