Enums and Pattern Matching

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 Pattern is 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