How to Use Match Guards in Rust

Match guards are boolean expressions appended to match arms using `if` that allow you to refine pattern matching without nesting additional `if` statements.

Match guards are boolean expressions appended to match arms using if that allow you to refine pattern matching without nesting additional if statements. They are evaluated only if the pattern matches, enabling complex conditional logic directly within the match structure while maintaining readability.

Use match guards when you need to combine pattern matching with additional conditions, such as checking a value's range or verifying a property before executing the arm. This keeps your code flat and avoids the "pyramid of doom" often caused by nested if blocks inside match arms.

Here is a practical example where we handle different user roles and ages:

fn check_access(role: &str, age: u32) -> &'static str {
    match (role, age) {
        ("admin", _) => "Full access",
        // Guard ensures we only match "user" if age is 18 or older
        ("user", age) if age >= 18 => "Standard access",
        ("user", age) if age < 18 => "Restricted access",
        _ => "No access",
    }
}

fn main() {
    println!("{}", check_access("user", 20)); // Standard access
    println!("{}", check_access("user", 15)); // Restricted access
    println!("{}", check_access("guest", 25)); // No access
}

In this example, the pattern ("user", age) matches any tuple starting with "user". The guard if age >= 18 then filters these matches. If the guard evaluates to false, the match engine continues to the next arm rather than executing the current one. Note that the variable age is bound in the pattern and is available for use in the guard expression.

You can also use guards to destructure complex types conditionally. For instance, when working with an enum that contains an Option:

enum Message {
    ChangeColor(u8, u8, u8),
    Move { x: i32, y: i32 },
    Quit,
}

fn process(msg: Message) {
    match msg {
        Message::ChangeColor(r, g, b) if r > 255 || g > 255 || b > 255 => {
            println!("Invalid color values");
        }
        Message::ChangeColor(r, g, b) => {
            println!("Setting color to ({}, {}, {})", r, g, b);
        }
        Message::Move { x, y } if x == 0 && y == 0 => {
            println!("No movement");
        }
        Message::Move { x, y } => {
            println!("Moving to ({}, {})", x, y);
        }
        Message::Quit => println!("Quitting"),
    }
}

Be careful with variable shadowing: variables bound in the pattern are available in the guard, but they do not leak into the arm's body if the guard fails. Also, remember that guards are evaluated lazily; if the pattern doesn't match, the guard expression is never executed, which is crucial for performance when the guard involves expensive computations.