Pattern-Matching

72. if let Guards — Pattern Match Inside Match Guards

Match guards are great for adding conditions to arms, but until now you couldn’t destructure inside them. Rust 1.95 stabilizes if let guards, letting you pattern-match right in the guard position.

Suppose you’re matching commands and need to parse a value from one of the variants. Before if let guards, you had to nest an if let inside the arm body:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
enum Command {
    Set(String, String),
    Get(String),
    Quit,
}

fn execute(cmd: Command) -> String {
    match cmd {
        Command::Set(key, value) => {
            if let Ok(n) = value.parse::<i32>() {
                format!("SET {key} = {n} (as integer)")
            } else {
                format!("SET {key} = {value} (as string)")
            }
        }
        Command::Get(key) => format!("GET {key}"),
        Command::Quit => "BYE".to_string(),
    }
}

The Set arm does two different things depending on whether the value parses as an integer — but that logic is buried inside a nested if let. With if let guards, the condition moves into the guard itself:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn execute_v2(cmd: Command) -> String {
    match cmd {
        Command::Set(key, value) if let Ok(n) = value.parse::<i32>() => {
            format!("SET {key} = {n} (as integer)")
        }
        Command::Set(key, value) => {
            format!("SET {key} = {value} (as string)")
        }
        Command::Get(key) => format!("GET {key}"),
        Command::Quit => "BYE".to_string(),
    }
}

Now each arm handles exactly one case. The guard destructures the parse result, and n is available in the arm body. If the guard fails, Rust falls through to the next Set arm — no nested if/else needed.

You can combine if let guards with regular boolean conditions using &&:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn execute_v3(cmd: Command) -> String {
    match cmd {
        Command::Set(key, value)
            if let Ok(n) = value.parse::<i32>()
            && n > 0 =>
        {
            format!("SET {key} = {n} (positive integer)")
        }
        Command::Set(key, value) => {
            format!("SET {key} = {value}")
        }
        Command::Get(key) => format!("GET {key}"),
        Command::Quit => "BYE".to_string(),
    }
}

This feature lands stable in Rust 1.95 (April 2026). If you’ve been nesting if let inside match arms, this is your cleanup opportunity.

46. Let Chains — Flatten Nested if-let with &&

Deeply nested if let blocks are one of Rust’s most familiar awkward moments. Rust 1.88 (2024 edition) finally fixes it: let chains let you &&-chain multiple let bindings and boolean guards in one if or while.

Before let chains, matching several optional values forced you to nest:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn describe(name: Option<&str>, age: Option<u32>) -> String {
    if let Some(n) = name {
        if let Some(a) = age {
            if a >= 18 {
                return format!("{n} is an adult");
            }
        }
    }
    "unknown".to_string()
}

Three levels of indentation just to check two Options and a condition. The actual logic is buried at the bottom.

With let chains, it collapses to a single if:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn describe(name: Option<&str>, age: Option<u32>) -> String {
    if let Some(n) = name
        && let Some(a) = age
        && a >= 18
    {
        format!("{n} is an adult")
    } else {
        "unknown".to_string()
    }
}

Bindings introduced in earlier lets are in scope for all subsequent conditions — n is available when checking a, and both are available in the body.

The same syntax works in while loops. This example processes tokens until it hits one it can’t parse:

1
2
3
4
5
6
7
8
9
let mut tokens = vec!["42", "17", "bad", "99"];
tokens.reverse(); // process front-to-back

while let Some(token) = tokens.pop()
    && let Ok(n) = token.parse::<i32>()
{
    println!("{n}");
}
// prints: 42, 17  — stops at "bad"

Short-circuit semantics apply: if any condition in the chain fails, Rust skips the rest and takes the else branch (or ends the while loop).

To enable let chains, set edition = "2024" in your Cargo.toml:

1
2
[package]
edition = "2024"

No unstable flags, no feature gates — it’s stable as of Rust 1.88 and available on the 2024 edition. If you’re still on 2021, this alone is a good reason to upgrade.

29. Let chains

Tired of deeply nested if let blocks? Rust 2024 edition brings let chains — chain multiple let patterns with && in a single if expression.

1
2
3
4
5
6
7
8
// Before: nested and hard to read
if let Some(a) = opt_a {
    if let Some(b) = opt_b {
        if a > 0 {
            println!("{a} + {b} = {}", a + b);
        }
    }
}

With let chains, flatten the whole thing:

1
2
3
4
5
6
if let Some(a) = opt_a
    && let Some(b) = opt_b
    && a > 0
{
    println!("{a} + {b} = {}", a + b);
}

Works with while too!

1
2
3
4
5
6
7
8
9
let mut iter = vec![Some(1), Some(2), None, Some(4)].into_iter();

while let Some(inner) = iter.next()
    && let Some(val) = inner
{
    println!("got: {val}");
}
// prints: got: 1, got: 2
// stops at None — the inner let fails

You can mix boolean expressions and let bindings freely. Each && can be either a regular condition or a pattern match.

Note: requires edition 2024 (edition = "2024" in Cargo.toml).