Rust if let and while let

A match expression must cover every possible case. Sometimes only one case matters, and the rest deserve a shrug. The if let and while let forms give a lighter way to handle one pattern. Imagine checking a mailbox: you only care whether a letter exists, and an empty box needs no ceremony.

The Problem with Verbose match

The code below cares only about the Some case, yet it must still write an arm for None.

let favorite: Option<&str> = Some("mango");

match favorite {
    Some(fruit) => println!("Favorite: {fruit}"),
    None => {}
}

Using if let

The if let form tests one pattern and runs the block when it matches.

if let Some(fruit) = favorite {
    println!("Favorite: {fruit}");
}
 favorite = Some("mango")
        |
        v
  Does it fit Some(fruit)?
     |yes                 |no
     v                    v
 run the block        skip silently

Read the line as: if the value fits the pattern Some(fruit), bind the inner value to fruit and run the block.

Adding an else Branch

An else block handles every value that fails the pattern.

let score: Option<u32> = None;

if let Some(s) = score {
    println!("Score is {s}");
} else {
    println!("No score recorded");
}
No score recorded

Working with Enums

The pattern can target any enum variant, including those carrying data.

enum Message {
    Quit,
    Write(String),
    Move { x: i32, y: i32 },
}

fn main() {
    let msg = Message::Write(String::from("hello"));

    if let Message::Write(text) = msg {
        println!("Text: {text}");
    }
}

Variants that do not match are ignored. The compiler does not demand arms for Quit or Move.

Trade-Off Against match

The shorter form skips the safety net of exhaustive checking. Adding a new enum variant later triggers no compiler warning at an if let site. Use match when every variant deserves a decision, and use if let when one variant matters.

Featurematchif let
Covers all casesRequiredOptional
Code lengthLongerShorter
Best forMany variantsOne variant

The while let Loop

The while let loop repeats as long as a pattern keeps matching. A stack of plates shows the idea: keep removing the top plate until none remain.

fn main() {
    let mut plates = vec![1, 2, 3];

    while let Some(top) = plates.pop() {
        println!("Removed plate {top}");
    }
}
Removed plate 3
Removed plate 2
Removed plate 1

The pop method returns Some(value) while items remain and None once the vector is empty. The loop stops by itself at that moment.

 pop() --> Some(3) --> body runs
 pop() --> Some(2) --> body runs
 pop() --> Some(1) --> body runs
 pop() --> None    --> loop ends

let else for Early Exit

The let else form unpacks a pattern and leaves the function when the pattern fails. The happy path stays flat and readable.

fn describe(input: Option<u32>) {
    let Some(value) = input else {
        println!("Nothing to show");
        return;
    };
    println!("Value: {value}");
}

fn main() {
    describe(Some(8));
    describe(None);
}
Value: 8
Nothing to show

The else block must leave the current flow with return, break, continue, or a panic.

Matching Results

The same syntax works for Result values when one outcome matters.

if let Ok(number) = "42".parse::<i32>() {
    println!("Parsed {number}");
}

Common Mistakes

Writing a comparison instead of a pattern is a frequent slip. The line if let Some(x) == value is invalid. The pattern sits on the left of a single equals sign, and the value sits on the right.

Using if let when every variant needs handling hides future bugs. A new enum variant compiles without a warning at an if let site. Switch to match in those places.

Creating an infinite while let loop happens when the expression never returns None. Verify that something inside the loop changes the data.

Practice Ideas

  • Search a vector with get(index) and print the item only when it exists.
  • Use while let to drain a vector of names and print each one.
  • Rewrite a three-line match on an Option as an if let.

Key Points

  • if let handles a single pattern without writing every match arm.
  • An optional else block covers all other values.
  • while let loops until the pattern stops matching.
  • let else exits early when a pattern fails.
  • Use match when each variant needs a decision.