let-else
A let-else statement (let Pattern = expression else { diverging_block };) matches a refutable pattern and binds its variables directly in the current surrounding scope; if the pattern does not match, the mandatory else block executes and must diverge (return, break, continue, or panic!).
Why let-else Eliminates Rightward Drift
Before let-else, extracting values from several Option or Result patterns required either nested if let blocks (pushing the happy path further to the right) or verbose match expressions that returned early:
Nested `if let` (Rightward Drift): Flat `let-else` (Happy Path Unindented):
if let Some(a) = step1() { let Some(a) = step1() else { return; };
if let Some(b) = step2(a) { let Some(b) = step2(a) else { return; };
if let Ok(c) = step3(b) { let Ok(c) = step3(b) else { return; };
use_value(c); use_value(c);
}
}
}
Guarding the Happy Path with let-else
Variables bound in the let Pattern are immediately in scope for all statements following the let-else statement:
#[derive(Debug)]
struct UserRecord {
id: u32,
username: String,
}
fn parse_user_line(line: &str) -> Result<UserRecord, String> {
// 1. Destructure `id_str` and `username` into the top-level function scope:
let Some((id_str, username)) = line.trim().split_once(':') else {
return Err(format!("missing ':' delimiter in '{line}'"));
};
// 2. Parse `id` or return an `Err` early:
let Ok(id) = id_str.trim().parse::<u32>() else {
return Err(format!("invalid numeric ID '{}'", id_str.trim()));
};
let username = username.trim();
if username.is_empty() {
return Err(String::from("username cannot be empty"));
}
Ok(UserRecord {
id,
username: String::from(username),
})
}
fn main() {
println!("{:?}", parse_user_line("42 : ferris"));
println!("{:?}", parse_user_line("invalid_line"));
println!("{:?}", parse_user_line("abc : ferris"));
}
Ok(UserRecord { id: 42, username: "ferris" })
Err("missing ':' delimiter in 'invalid_line'")
Err("invalid numeric ID 'abc'")
Using let-else Inside Loops with continue or break
Inside a for, while, or loop body, the else block of a let-else statement can skip malformed items with continue or exit the loop with break:
enum Event {
Heartbeat,
Metric { name: &'static str, value: i32 },
Shutdown,
}
fn main() {
let stream = [
Event::Heartbeat,
Event::Metric { name: "requests", value: 120 },
Event::Heartbeat,
Event::Metric { name: "errors", value: 2 },
Event::Shutdown,
];
for event in &stream {
let Event::Metric { name, value } = event else {
// Skip non-metric events without indenting the rest of the loop body:
continue;
};
println!("Recorded metric '{name}' = {value}");
}
}
Recorded metric 'requests' = 120
Recorded metric 'errors' = 2
The else Block Must Diverge (E0308)
Because the variables bound by let Pattern would be uninitialized if execution were allowed to fall through the else block into the rest of the function, the compiler requires the else block to evaluate to the never type !:
// ❌ does not compile
fn print_number(opt: Option<i32>) {
let Some(n) = opt else {
println!("No number provided");
// Missing `return;`! Execution cannot fall through to `println!("{n}")`
};
println!("Number: {n}");
}
fn main() {}
error[E0308]: `else` clause of `let...else` does not diverge
--> src/main.rs:2:28
|
2 | let Some(n) = opt else {
| ____________________________^
3 | | println!("No number provided");
4 | | // Missing `return;`! Execution cannot fall through to `println!("{n}")`
5 | | };
| |_____^ expected `!`, found `()`
|
= note: try adding `return`, `break`, or `panic!` at the end of the `else` block