Match Guards
A match guard is an additional boolean condition (if boolean_expr) written after the pattern in a match arm (Pattern if condition => body). The arm is selected only if the pattern matches and the guard expression evaluates to true; if the guard evaluates to false, execution continues to the next match arm.
Syntax and Evaluation Order
Arm Evaluation with a Match Guard:
Scrutinee ──> Does `Pattern` match? ──No───> Try next arm
│
Yes
v
Evaluate `if guard` ──False─> Try next arm
│
True
v
Execute arm `=> body`
Inside the guard expression if condition:
- Any variable bound by
Patternis accessible by shared reference (even if the pattern will move the value once the arm body is entered, so evaluating a failing guard never prematurely moves the scrutinee!). - Outer variables from the enclosing scope can also be compared against pattern variables—solving the problem where a bare identifier in a pattern would otherwise create a new variable binding instead of comparing against an existing variable.
fn check_temperature(reading: Option<i32>, threshold: i32) {
match reading {
Some(t) if t >= threshold => println!("CRITICAL: {t}°C meets or exceeds threshold ({threshold}°C)"),
Some(t) if t < 0 => println!("FREEZING: {t}°C is below zero"),
Some(t) => println!("NORMAL: {t}°C"),
None => println!("Sensor offline"),
}
}
fn main() {
check_temperature(Some(95), 90);
check_temperature(Some(-5), 90);
check_temperature(Some(24), 90);
check_temperature(None, 90);
}
CRITICAL: 95°C meets or exceeds threshold (90°C)
FREEZING: -5°C is below zero
NORMAL: 24°C
Sensor offline
Precedence of Match Guards with Or-Patterns (|)
When a match guard follows an or-pattern (A | B | C if cond => ...), the guard applies to every alternative ((A | B | C) if cond), not only to C:
fn main() {
let pair = (2, false);
match pair {
// Applies `if enabled` to ALL of `1 | 2 | 3`:
(1 | 2 | 3, enabled) if enabled => println!("Active low channel"),
_ => println!("Inactive or out of range"),
}
}
Inactive or out of range
How Match Guards Affect Exhaustiveness Checking
Because a match guard can contain arbitrary runtime expressions and function calls, the compiler does not attempt to prove that a set of guarded arms covers all cases, except when the guard is a literal true. Even if two guarded arms (Some(x) if x >= 0 and Some(x) if x < 0) are mathematically complementary, you must provide an unguarded fallback arm:
// ❌ does not compile
fn sign(n: Option<i32>) -> &'static str {
match n {
Some(x) if x >= 0 => "non-negative",
Some(x) if x < 0 => "negative",
None => "none",
}
}
fn main() {}
error[E0004]: non-exhaustive patterns: `Some(_)` not covered
--> src/main.rs:2:11
|
2 | match n {
| ^ pattern `Some(_)` not covered
|
= note: the matched value is of type `Option<i32>`
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
5 ~ None => "none",
6 ~ Some(_) => todo!(),
|
Fix: Drop the Redundant Guard on the Second Case (or Use an Inclusive Range Pattern)
Either omit the guard on the complementary Some(_) arm (Some(x) if x >= 0 followed by Some(_)), or replace the guard with an exhaustive range pattern (Some(0..=i32::MAX) and Some(i32::MIN..=-1)):
fn sign(n: Option<i32>) -> &'static str {
match n {
Some(0..=i32::MAX) => "non-negative",
Some(i32::MIN..=-1) => "negative",
None => "none",
}
}
fn main() {
println!("sign(Some(10)) = {}", sign(Some(10)));
println!("sign(Some(-3)) = {}", sign(Some(-3)));
}
sign(Some(10)) = non-negative
sign(Some(-3)) = negative