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 |