🎓 A1Learner
🔍

Rust Let Else

let ... else unwraps a pattern and keeps the bound names for the rest of the function. The else block runs when the pattern fails.

The nested way: if let wraps the work

With if let, the names live only inside the block, so the real work nests inside it:

let input: Option<&str> = Some("tea");
if let Some(drink) = input {
    println!("drink = {drink}");
} else {
    println!("no drink chosen");
}

Bind the names, then just carry on

The names bound by let ... else live in the code after the line — no extra block, no nesting. The else side runs only when the pattern fails:

let input: Option<&str> = Some("tea");
let Some(drink) = input else {
    println!("no drink chosen");
    return;
};
println!("drink = {drink}");

The else block must leave

The else block must diverge — leave by return, break, continue or panic!, and never fall through to the next line. One that just prints and ends is refused:

let Some(x) = input else {
    println!("nothing here");
};
// error E0308: `else` clause of `let...else` does not diverge

A whole tuple pattern in one line

Two Options can be tested together in a single line. Both names bind, or the else block leaves:

let pair = (Some(1), Some(2));
let (Some(a), Some(b)) = pair else {
    println!("both are needed");
    return;
};
println!("{} + {} = {}", a, b, a + b);

It works for Result too

An Ok pattern keeps the good value on the main path, and the bad case leaves early:

let text = "42";
let Ok(count) = text.parse::<u32>() else {
    println!("not a number");
    return;
};
println!("count = {count}");

Example

fn split_count(text: &str) -> (u64, &str) {
    let mut parts = text.splitn(2, ' ');
    let (Some(count_text), Some(item)) = (parts.next(), parts.next()) else {
        return (0, "");
    };
    let Ok(count) = count_text.parse::<u64>() else {
        return (0, "");
    };
    (count, item)
}
fn main() {
    let (n, item) = split_count("3 chairs");
    println!("{} x {}", n, item);
}

Key points

  • let ... else has been stable since Rust 1.65, so it works in any current project

In this example, split_count reads text like "3 chairs". The first let ... else tests two Options at once: (Some(count_text), Some(item)). Both are present. count_text is "3" and item is "chairs". Both names stay alive for the rest of the function. The second let ... else parses "3" as a number with an Ok pattern. It succeeds, so count is 3. The function returns (3, "chairs"), and main prints 3 x chairs. Had either step failed, its else block would have left the function at once, handing back the empty pair.

Note

Reach for let ... else at the top of a function, as a gate.

Reach for if let when both sides have real work to do, not just an early exit.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What must the else block of a let ... else always do?