Enums and Pattern Matching

if let

The if let expression tests a scrutinee against a single refutable pattern and runs its block only when the pattern matches, eliminating the boilerplate of a two-arm match expression where one arm is _ => ().

Equivalence to a Two-Arm match

// Verbose `match` for a single variant:      // Concise `if let`:
match config_max {                            if let Some(max) = config_max {
    Some(max) => println!("Max = {max}"),         println!("Max = {max}");
    _ => (),                                  }
}

Unlike match, if let is intentionally non-exhaustive: its purpose is to handle the one pattern you care about and ignore all other cases (or handle them in an optional else / else if / else if let chain).

Basic if let and else Branches

Like a regular if expression, if let can include an else block and can evaluate to a value when both the if let block and else block return the same type:

enum AuthState {
    Anonymous,
    LoggedIn { username: String, is_admin: bool },
}

fn greeting(state: &AuthState) -> String {
    if let AuthState::LoggedIn { username, is_admin: true } = state {
        format!("Welcome back, Administrator {username}!")
    } else if let AuthState::LoggedIn { username, .. } = state {
        format!("Hello, {username}!")
    } else {
        String::from("Hello, guest!")
    }
}

fn main() {
    let admin = AuthState::LoggedIn {
        username: String::from("root"),
        is_admin: true,
    };
    let member = AuthState::LoggedIn {
        username: String::from("alice"),
        is_admin: false,
    };

    println!("{}", greeting(&admin));
    println!("{}", greeting(&member));
    println!("{}", greeting(&AuthState::Anonymous));
}
Welcome back, Administrator root!
Hello, alice!
Hello, guest!

Combining if let Conditions with && (let-Chains in Rust 2024)

In the Rust 2024 edition (stabilized in Rust 1.88), if let patterns can be chained with boolean expressions and additional let patterns using the && logical AND operator (let-chains), avoiding deeply nested if let blocks:

fn process_header(header: Option<&str>) {
    // Rust 2024 `let`-chain combining two `let` patterns and a boolean guard with `&&`:
    if let Some(raw) = header
        && let Some((key, value)) = raw.split_once(':')
        && !value.trim().is_empty()
    {
        println!("Header '{}' = '{}'", key.trim(), value.trim());
    } else {
        println!("Invalid or empty header: {header:?}");
    }
}

fn main() {
    process_header(Some("Authorization: Bearer tok_123"));
    process_header(Some("X-Empty:   "));
    process_header(None);
}
Header 'Authorization' = 'Bearer tok_123'
Invalid or empty header: Some("X-Empty:   ")
Invalid or empty header: None

Using Or-Patterns (|) Inside if let

An if let pattern can use | to match multiple variants at once:

enum Status {
    Pending,
    Retrying(u8),
    Done,
}

fn is_in_flight(s: &Status) -> bool {
    if let Status::Pending | Status::Retrying(_) = s {
        true
    } else {
        false
    }
}

fn main() {
    println!("Retrying(2) in flight? {}", is_in_flight(&Status::Retrying(2)));
    println!("Done in flight?        {}", is_in_flight(&Status::Done));
}
Retrying(2) in flight? true
Done in flight?        false

When to Prefer if let vs. match vs. let ... else

Construct Best Used When
if let Pat = expr { ... } You only want to act on 1 (or 2) specific patterns and optionally run an else block
let Pat = expr else { return ... }; You want to unwrap a refutable pattern into the outer scope and diverge (return/break/continue) if it fails
match expr { ... } You want the compiler to enforce exhaustiveness across all variants of an enum