if let
if let is the idiom for running code when one refutable pattern matches while intentionally ignoring the nonmatching cases.
What it is
if let PATTERN = expression { ... } combines an if branch with pattern matching.
It is equivalent to a small match with one meaningful arm and a do-nothing fallback.
It can also have an else branch for the nonmatching case.
Use it when only one case is interesting.
Common examples include Some(value), Ok(value), or one enum variant with a payload.
How it works
Rust evaluates the expression and compares it with the pattern.
If the pattern matches, any bindings in the pattern are available inside the block.
If the pattern does not match, the block is skipped or the else block runs.
The tradeoff is that if let does not check Exhaustiveness.
That is the point when you truly mean “ignore the rest.”
When every case matters, use The match Expression instead.
if let is syntax for one pattern test, not a separate kind of destructuring. The pattern has the
same binding and ownership behavior it would have in a match arm. Matching an owned
Option<String> with if let Some(text) = value moves the string; matching value.as_ref() gives
you Option<&String> and keeps the original option usable.
An else block handles all nonmatching cases as one group. If those cases are not equivalent, switch
to match so the compiler and the reader can see every branch.
Example
fn print_limit(limit: Option<u8>) -> String {
let mut message = String::from("no limit");
if let Some(value) = limit {
message = format!("limit is {value}");
}
message
}
fn main() {
assert_eq!(print_limit(Some(3)), "limit is 3");
assert_eq!(print_limit(None), "no limit");
}Worked example
enum Event {
Connected { peer: String },
Disconnected,
Data(Vec<u8>),
}
fn maybe_peer(event: &Event) -> Option<&str> {
if let Event::Connected { peer } = event {
return Some(peer.as_str());
}
None
}
fn main() {
let event = Event::Connected {
peer: String::from("cache-1"),
};
assert_eq!(maybe_peer(&event), Some("cache-1"));
assert_eq!(maybe_peer(&Event::Disconnected), None);
}Common errors
Moving a payload in if let can make the original value unavailable:
error[E0382]: borrow of partially moved valueFix it by matching a reference (if let Some(text) = value.as_ref()) or by using ref in the pattern
when you are deliberately matching the owned value but borrowing one field.
Best practice
- ✅ Use
if letfor a single interesting success case with small branch logic. - ✅ Use
elseonly when the fallback is simple and directly paired with the pattern. - ✅ Switch to
matchwhen there are several meaningful alternatives. - ✅ Use
as_ref()oras_mut()beforeif letwhen you want to inspect or mutate without consuming. - ✅ Prefer let else when the nonmatching case should immediately exit and the success binding is needed afterward.
Pitfalls
- ⚠️ Do not use
if letwhen forgetting a case would be a bug; use The match Expression for Exhaustiveness. - ⚠️ Watch for shadowing introduced by pattern bindings; see Pattern Variable Shadowing.
- ⚠️ Do not check
is_some()beforeif let; the pattern already performs the test. - ⚠️ Do not grow an
if let/else if letladder until it encodes a hidden state machine; usematch. - ⚠️ Avoid
if let Some(_) = optionwhenoption.is_some()is the clearer predicate and no binding is needed.
See also
Option · Patterns · Refutable and Irrefutable Patterns · The match Expression · let else · Exhaustiveness · Pattern Variable Shadowing · Match Guards · Catch-All and Wildcard Patterns · Enums & Pattern Matching
Sources
- The Rust Programming Language, ch. 6.3 “Concise Control Flow with if let and let…else” - the-book, https://doc.rust-lang.org/book/ch06-03-if-let.html
- The Rust Programming Language, ch. 19.1 “Conditional if let Expressions” - the-book, https://doc.rust-lang.org/book/ch19-01-all-the-places-for-patterns.html#conditional-if-let-expressions
