Control-Flow

207. if let Guards — Match a Pattern Inside a Guard, and Still Fall Through

A match arm whose extra check is itself a fallible if let used to force you to nest a second match — and duplicate the fallback. Rust 1.95 lets the guard do the binding.

Say a config field is a raw string and you want to parse it as a port, defaulting when it’s absent or garbage:

1
2
3
4
enum Field {
    Raw(String),
    Missing,
}

Before if let guards, a guard could only hold a bool expression — it couldn’t bind. So the parse had to move inside the arm body, and the fallback ended up written twice:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn to_port(f: &Field) -> u16 {
    match f {
        Field::Raw(s) => {
            if let Ok(p) = s.parse::<u16>() {
                p
            } else {
                8080 // fallback #1: Raw but unparseable
            }
        }
        Field::Missing => 8080, // fallback #2: same value, second copy
    }
}

Two copies of 8080 that have to stay in sync. The nested if let can’t fall through to another arm — once you’re in Field::Raw, you’re stuck handling everything there.

Since Rust 1.95, if let works directly in the guard. If the pattern fails to match, the arm is skipped and matching continues to the next arm:

1
2
3
4
5
6
fn to_port(f: &Field) -> u16 {
    match f {
        Field::Raw(s) if let Ok(p) = s.parse::<u16>() => p,
        _ => 8080, // one fallback, covers Missing AND unparseable Raw
    }
}

The guard both tests and binds p, and a failed parse simply drops to the catch-all. One fallback, no duplication.

It composes with && too, so you can chain several binds in one guard:

1
2
3
4
5
6
7
8
9
fn first_two(line: &str) -> Option<(i32, i32)> {
    let mut it = line.split(',');
    match (it.next(), it.next()) {
        (Some(a), Some(b))
            if let Ok(x) = a.trim().parse::<i32>()
            && let Ok(y) = b.trim().parse::<i32>() => Some((x, y)),
        _ => None,
    }
}

Reach for it whenever a match arm needs a second, fallible look at its data and you want a clean failure to roll into the next arm instead of a pyramid of nested matches.