blog.dopana

Back

Rust treats loops as powerful, expressive control flow constructs. Beyond standard for and while loops, Rust offers loop expressions that return values, while let for draining data with pattern matching, and labeled loops for nested jumps.

Explain Like I’m 10: The Vending Machine & Pez Dispenser#

Imagine two different snack mechanisms:

  1. The Vending Machine (loop with break value): You keep inserting coins in a loop until you hit the jackpot; once reached, the machine ejects your prize directly into your hands (break prize).
  2. The Candy Dispenser (while let Some(candy)): Every time you tilt the dispenser head back, you check if there is a candy ready. As long as candy pops out (Some(candy)), you eat it. The exact moment the dispenser is empty (None), the loop stops automatically.
graph TD
    Start["Start Loop"] --> PatternMatch{"Does expression match pattern?<br/>(e.g., Some(item))"}
    PatternMatch -->|Yes: Extract item| LoopBody["Execute Loop Body with item"]
    LoopBody --> NextEval["Evaluate next item / state"]
    NextEval --> PatternMatch
    PatternMatch -->|No: None / Mismatch| Terminate["Exit Loop gracefully"]

loop as an Expression: Returning Values#

Unlike many C-style languages where loops are purely statements, Rust’s loop is an expression. You can pass a value to break to return it from the loop:

graph LR
    LStart["Enter loop"] --> Work["Execute retry attempt"]
    Work --> Check{"Success condition met?"}
    Check -->|No| Work
    Check -->|Yes: break value| ReturnVal["result = evaluated value"]

while let: Looping as Long as a Pattern Matches#

When popping elements from stacks, queues, channels, or iterators, traditional loops require awkward unwrapping. while let runs the loop body as long as the pattern successfully matches:

Without while let (Verbose loop + match)#

src/main.rs
let mut stack = vec![1, 2, 3];

loop {
    match stack.pop() {
        Some(top) => println!("Popped: {top}"),
        None => break, // Boilerplate exit
    }
}
rust

With while let (Clean & Idiomatic)#

src/main.rs
let mut stack = vec![1, 2, 3];

// Loops as long as stack.pop() returns Some(top)
while let Some(top) = stack.pop() {
    println!("Popped: {top}");
}
rust

[!TIP] Use while let whenever you are draining, popping, or receiving items from an iterator, channel receiver (receiver.recv()), or stack until it yields None or Err.

Loop Labels: Disambiguating Nested Loops#

When dealing with nested loops, break and continue apply to the innermost loop by default. You can specify a loop label (starting with a single quote ') to break or continue an outer loop:

sequenceDiagram
    participant Outer as OuterLoop
    participant Inner as InnerLoop
    
    Outer->>Inner: Enter inner loop
    Inner->>Inner: remaining == 9: break inner
    Inner-->>Outer: Return to outer loop
    Outer->>Inner: Enter inner loop with count 2
    Inner->>Outer: break counting_up: Jump out of outer loop

Summary Comparison of Loop Constructs#

ConstructPrimary Use CaseSupports break value?Pattern Matching?
loopInfinite loops, retry mechanisms, worker threadsYesOptional with match inside
while conditionStandard boolean conditional loopingNoNo
while let patternDraining Option / Result collectionsNoYes (auto-binds)
for item in iterIterating over known sequences or rangesNoYes (destructures items)

Summary#

  • loop can return computed values upon exit with break value.
  • while let cleanly replaces verbose loop + match boilerplate for sequence consumption.
  • Loop labels ('label: loop) grant precise control over nested multi-tier loop jumps.

References#