match is Rust's most powerful branching tool. It looks similar to C/Java's switch, but it has an exhaustiveness check, so a compile error occurs if all possibilities are not handled. You can also deconstruct and bind data within patterns, making it naturally suitable for handling enums and Option. if let/while let are shorthand syntaxes for handling only one variant briefly.
Learning Objectives
- Uses all branching, binding, wildcards, and guards of
match. - Understands that thanks to the exhaustiveness check, missing branches are caught at compile time.
- Writes code for a single variant briefly using
if let. - Handles loops returning an Option naturally with
while let.
Key Concepts
1) Basic Structure of match
match value {
Pattern1 => Expression1,
Pattern2 => Expression2,
_ => DefaultExpression, //The rest
}
- All branches must return the same type of value (expression).
- Exhaustiveness: compile error if not all possibilities are handled.
_wildcard is a safety net.
2) Data Binding
enum Coin { Quarter(String), /* ... */ }
match c {
Coin::Quarter(state) => println!("from {state}"),
_ => (),
}
Assign a name to the internal data of a variant to use it within a branch.
3) Guard if
match score {
s if s >= 90 => 'A',
s if s >= 80 => 'B',
_ => 'F',
}
You can attach additional conditions separate from the pattern. Only the first branch whose guard passes is selected.
4) if let
if let Some(n) = maybe {
println!("{n}");
}
It's the same as match maybe { Some(n) => ..., None => () } but shorter and clearer. else clause is also possible.
5) while let
while let Some(top) = stack.pop() {
println!("{top}");
}
The loop ends the moment matching fails. It naturally combines with a method that returns Option.
Examples
Example 1 — ex01_match.rs: match, binding, guard
//Example 1 of Chapter 13: match — pattern branching, binding, wildcard, guard
enum Coin {
Penny,
Nickel,
Dime,
Quarter(String), //Which state's quarter
}
fn describe(c: &Coin) -> String {
match c {
Coin::Penny => String::from("1 cent"),
Coin::Nickel => String::from("5 cents"),
Coin::Dime => String::from("10 cents"),
//Bind the internal data of variant
Coin::Quarter(state) => format!("25 cents ({state} state)"),
}
}
fn grade(score: i32) -> char {
//_ wildcard + guard (if condition)
match score {
s if s >= 90 => 'A',
s if s >= 80 => 'B',
s if s >= 70 => 'C',
_ => 'F',
}
}
fn main() {
println!("{}", describe(&Coin::Penny));
println!("{}", describe(&Coin::Quarter(String::from("Alaska"))));
for s in [95, 82, 73, 50] {
println!("{s} → {}", grade(s));
}
}
Example 2 — ex02_if_let.rs: if let
//Example 2 of Chapter 13: if let — concise syntax when handling only one variant
//
//Compared to match, suitable when 'ignoring the rest' feels natural.
fn main() {
let maybe = Some(42);
//Handled with match
match maybe {
Some(n) => println!("(match) value = {n}"),
None => {}
}
//Same operation shortened with if let
if let Some(n) = maybe {
println!("(if let) value = {n}");
}
//else is also possible
let nothing: Option<i32> = None;
if let Some(n) = nothing {
println!("value = {n}");
} else {
println!("(if let else) no value");
}
}
Example 3 — ex03_while_let.rs: while let
//Example 13-3: while let — Repeat while a pattern matches
//
//It works naturally with methods that return an Option, like Vec's pop().
fn main() {
let mut stack = vec![1, 2, 3, 4];
//Remove the last element until the stack is empty.
while let Some(top) = stack.pop() {
println!("Popped: {top}");
}
println!("Final = {:?}", stack);
}
Common Mistakes
Q. Shouldn't we always put a _ branch in match?
A. Having _ satisfies exhaustiveness, but the compiler won't notify you if a new variant is added. There is a risk of potential missed handling, so list specific variants if possible.
Q. Which is better, if let or match?
A. If there are 2 or more variants and all need to be handled, use match. If you only handle one variant and ignore the rest, use if let. Readability is the deciding factor.
Q. Can I create new variables inside match?
A. The branch body is an ordinary code block, so you can freely use let. Variables bound in the pattern itself are only valid within the branch body.
Q. Does a guard (if) perform exhaustiveness checking?
A. A guard checks an additional condition after pattern matching, so it does not affect exhaustiveness. Even if all guards evaluate to false, the compiler cannot know, so it is good practice to place a safety net like _ at the end when using guards.
Summary
matchis a powerful branching tool that supports exhaustiveness, binding, and guards.if letis a shorthand syntax that handles only one variant briefly.while letis a repetitive pattern combined with methods that return an Option.- Wildcard
_is convenient, but be aware that you may lose notifications when adding new variants.
Try it yourself
Solve the assignment below. The reference solution is shown below.