🎓 A1Learner
🔍

Rust Match Guards

A match guard is an extra if test on a match arm. A guard checks a value the shape cannot describe.

The long way: an if inside the arm

Without a guard, the extra test nests inside the arm:

let age = Some(15);
match age {
    Some(a) => {
        if a >= 18 {
            println!("adult");
        } else {
            println!("minor, age {a}");
        }
    }
    None => println!("no age given"),
}

A guard adds a test a pattern cannot say

A pattern checks the shape. The guard after if checks a value. Here the shape matches but the guard fails, so Rust moves on to the next arm:

let age = Some(15);
match age {
    Some(a) if a >= 18 => println!("adult"),
    Some(a) => println!("minor, age {a}"),
    None => println!("no age given"),
}

A guard can see variables from outside

The pattern binds a fresh name n, and the guard compares it with the outer target. Writing Some(target) in the pattern binds a new target. It does not compare. The guard is the escape hatch for that trap:

let target = 10;
let value = Some(10);
match value {
    Some(n) if n == target => println!("hit the target"),
    _ => println!("missed"),
}

One guard covers the whole or-pattern

In 4 | 5 | 6 if y, the guard belongs to all three choices, not just the 6. x is 5, so the pattern matches — but y is false, so the whole arm is skipped:

let x = 5;
let y = false;
match x {
    4 | 5 | 6 if y => println!("yes"),
    _ => println!("no"),
}

Example

fn main() {
    let num = Some(4);
    match num {
        Some(x) if x % 2 == 0 => println!("The number {} is even", x),
        Some(x) => println!("The number {} is odd", x),
        None => println!("no number"),
    }
    let target = 10;
    let value = Some(10);
    match value {
        Some(n) if n == target => println!("matched the target {}", target),
        _ => println!("something else"),
    }
    let x = 4;
    let y = false;
    match x {
        4 | 5 | 6 if y => println!("yes"),
        _ => println!("no"),
    }
}

Key points

  • Guards exist only in match: if let and while let have none — put the extra test inside the block instead
  • When a guard is present, the compiler cannot check exhaustiveness for that arm.
  • Keep a catch-all arm.
  • The catch-all arm is the safety net.
  • A guard can test whether a number is even, or whether it equals a setting stored elsewhere.

In this example, three guards do three jobs. First, Some(x) matches Some(4) and the guard x % 2 == 0 is true, so the even line prints. Second, the pattern binds a fresh name n. The guard compares it with the outer target, 10. They match. So matched the target 10 prints. Writing Some(target) instead would bind a new target. It would have matched anything. Third, x is 4. So the pattern 4 | 5 | 6 matches. But the guard if y is false. It is false for the whole pattern. So the arm is skipped. The catch-all prints no.

Note

A pattern alone cannot say "equal to that variable over there".

Naming a variable in a pattern creates a binding.

The binding is always a new binding.

The guard is the escape hatch built for this trap.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

In the arm 4 | 5 | 6 if y => ..., what does the guard if y apply to?