Skip to content

Pattern Matching

match compares a value against a series of patterns and executes the arm whose pattern matches. Unlike a C switch, Rust’s match is an expression — it produces a value — and it is checked exhaustively by the compiler.

let x: i32 = 3;
let description = match x {
1 => "one",
2 => "two",
3 => "three",
_ => "something else",
};
println!("{}", description); // three

The _ wildcard matches any value you have not listed explicitly. Without it (and without covering all cases), the code will not compile.

The real power of match comes from destructuring enum variants and binding their data to local names:

enum Coin {
Penny,
Quarter(String),
}
match coin {
Coin::Penny => println!("Penny"),
Coin::Quarter(state) => println!("Quarter from {}", state),
}

The identifier state in Coin::Quarter(state) is a binding — it receives the String value carried by that variant. You can use it freely inside the arm body.

Patterns can match integer ranges with ..=:

match score {
90..=100 => "A",
80..=89 => "B",
_ => "C or below",
}

_ is a catch-all that matches anything without binding it. Place it last; any arm after _ would be unreachable and the compiler warns you.

When you only care about one variant, if let is more concise than a full match:

let temperature = Some(22);
if let Some(t) = temperature {
println!("Temperature: {}C", t);
}

This binds t to the inner value if the pattern matches, and does nothing on None. You can attach an else branch for the non-matching case.

A guard is an extra if condition on a match arm. It further narrows which values the arm accepts:

match number {
n if n < 0 => println!("negative: {}", n),
0 => println!("zero"),
n => println!("positive: {}", n),
}

Guards run after the structural pattern matches, so the binding (n) is available inside the guard condition.

#[derive(Debug)]
enum Coin {
Penny,
Nickel,
Dime,
Quarter(String),
}
fn value_in_cents(coin: &Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("Quarter from {}!", state);
25
}
}
}
fn describe_number(n: i32) -> &'static str {
match n {
0 => "zero",
1..=9 => "single digit",
10..=99 => "double digit",
_ => "large",
}
}
fn main() {
let coins = vec![
Coin::Penny,
Coin::Nickel,
Coin::Quarter(String::from("Alaska")),
Coin::Dime,
];
for coin in &coins {
println!("{:?} = {} cents", coin, value_in_cents(coin));
}
for n in [0, 7, 42, 100] {
println!("{} is {}", n, describe_number(n));
}
let temperature = Some(22);
if let Some(t) = temperature {
println!("Temperature: {}C", t);
}
}
What happens if a match expression does not cover all possible values of the matched type?
What does the _ pattern do in a match arm?
When should you prefer if let over a full match expression?
What is a match guard?