R
RUSTY YELLOW PAGES

if letKeyword


Explanation

if let PATTERN = EXPR { ... } tests whether EXPR matches PATTERN. If it does, any names PATTERN introduces are bound within the following block. It differs from a plain if in what's being tested: a plain if requires a bool condition, while if let succeeds or fails based on whether a pattern matches — the two are not interchangeable, and if let exists precisely for patterns that don't reduce to a single boolean. See if let / while let for the deeper "why" — when this lighter form is preferable to a full match.

An if let can be followed by further pattern tests chained with else if let PATTERN_2 = EXPR_2 { ... }, and a final plain else { ... } for whatever matched none of the preceding patterns — the basic usage example below shows the full three-part chain.

This is the same "just an if nested inside the previous else" shape described on the else page, extended here to pattern tests instead of boolean conditions — each else if let tries a new pattern (possibly against a different expression) only once the ones before it have failed.

if let/else vs. let else: these look related but solve different problems. if let PATTERN = EXPR { A } else { B } produces two branches, A and B, that both continue executing normally afterward — the pattern's success or failure only decides which of two live paths runs, and code after the whole construct is reached from either one. let PATTERN = EXPR else { ... }; (see else for its full treatment) is shaped differently: it isn't a fork with two continuing paths, it's a single binding that either succeeds — falling through to the rest of the enclosing scope with PATTERN's names in scope — or fails and runs the else block, which is required to diverge (return, break, continue, panic!, or similar). There is no third option where let else's else block finishes normally and execution continues past the let, because the rest of the scope depends on PATTERN's bindings having been established. Reach for if let/else when both outcomes have real, continuing code to run; reach for let else when the non-matching case is a bail-out and the matching case is meant to keep going with the bound value in scope.

Like if, an if let/else chain is an expression when every branch produces the same type — it can sit on the right of a let, though this is less common than using it purely for its side effects.

Usage examples

Chaining if let / else if let / else

enum ConfigValue {
    Number(i64),
    Text(String),
    Missing,
}

let value = ConfigValue::Text("info".to_string());

if let ConfigValue::Number(n) = &value {
    // <- `if let` tests the pattern against `&value`
    println!("number: {n}");
} else if let ConfigValue::Text(s) = &value {
    // <- `else if let`: a second, different pattern tried only if the first failed
    println!("text: {s}");
} else {
    println!("missing"); // <- runs only if neither pattern above matched
}

Branching on data (pattern matching)

A network frame handler needs different handling for a data payload versus a close frame, with anything else treated as a keepalive — if let/ else if let walks through the shapes it actually cares about.

enum Frame {
    Data(Vec<u8>),
    Close(u16),
    Ping,
}

fn describe(frame: &Frame) -> String {
    if let Frame::Data(bytes) = frame {
        // <- `if let` matches the Data shape and binds `bytes`
        format!("{} bytes of data", bytes.len())
    } else if let Frame::Close(code) = frame {
        // <- `else if let` tries the Close shape next
        format!("closing with code {code}")
    } else {
        "ping".to_string()
    }
}

Each pattern is tried in order until one matches, which reads as "which of these shapes is it" without the ceremony of a match's arms for a case this small — the Rust Book introduces if let for exactly this "only some shapes need code" situation.

Handling and propagating errors

Reporting a metrics data point is best-effort: the caller only wants to log a failure, not stop the program over it — if let Err(...) isolates just that one case.

fn report_metric(result: Result<(), String>) {
    if let Err(reason) = result {
        // <- only the failure case needs a reaction; success falls through silently
        eprintln!("metric send failed: {reason}");
    }
}

When the success case genuinely needs no follow-up, if let Err(...) avoids writing an Ok(()) => {} arm that would say nothing — the same "only one shape matters here" reasoning the Rust Book gives for preferring if let over a full match when only one outcome needs handling.

Working with collections

Looking up a product's stock count only has something useful to say when the SKU is known — if let Some(...) handles the found case, with else covering an unrecognized SKU.

use std::collections::HashMap;

fn describe_stock(inventory: &HashMap<String, u32>, sku: &str) -> String {
    if let Some(&count) = inventory.get(sku) {
        // <- `if let` handles only the "found" case
        format!("{sku}: {count} in stock")
    } else {
        format!("{sku}: unknown SKU")
    }
}

HashMap::get returns Option<&V> specifically so a missing key is an ordinary value to match on rather than a panic — if let/else gives both outcomes (found, not found) a place to go without the extra None => ... arm a full match would need here.

Explanation

Embedded support: Full

if let behaves identically under #![no_std] — core-language pattern matching, allocator-free, compiling to the same code a hand-written match would. It's a particularly good fit for embedded driver code: a non-blocking peripheral read commonly returns Option<T> ("a byte is available" vs. "nothing yet"), and if let Some(...) handles the one case that matters without a wildcard arm that does nothing.

Usage examples

Reading a byte only when one is available

if let Some(byte) = uart.read() {
    // <- `if let` handles only the case where a byte is actually available
    buffer.push(byte);
}

Reacting only to a sensor fault

fn handle_reading(result: Result<f32, SensorError>) {
    if let Err(_fault) = result {
        // <- only the failure case needs a reaction; a good reading falls through silently
        set_fault_led(true);
    }
}