← thecodex.expert · The Codex Family of Knowledge
Rust

Control Flow

break can return a value from a loop in Rust — an unusual feature most languages don’t have at all.

Rust 1.95+ if is an expression Last verified:
Where this bites you

A for loop's range bound looks harmless when it's computed with ordinary subtraction, like "all indices except the last" via len() - 1. The moment the collection is empty, that subtraction underflows — 0 - 1 on an unsigned usize has no negative result to fall back to. Debug builds catch it with a panic. Release builds don't: the subtraction silently wraps around to the type's maximum value, turning a loop meant to run zero times into one that tries to run billions of times instead.

Rustunderflow_bug.rs
fn main() {
    let items: Vec<i32> = vec![];
    for i in 0..(items.len() - 1) {
        println!("{}", i);
    }
    println!("done");
}
// debug:   thread 'main' panicked at src/main.rs:3:17: attempt to subtract with overflow
// release: items.len() - 1 wraps to 18446744073709551615 (usize::MAX) -- the loop
//          never reaches 'done', it just keeps counting
Canonical Definition

Rust’s if is an expression, not just a statement — it can produce a value usable directly in a let binding, provided every branch produces the same type. Rust has three loop keywords: loop (infinite, until an explicit break), while (condition-based), and for (iterating a range or collection) — and uniquely, break inside a loop can carry a value out of the loop entirely.

if as an expression

Because if/else is an expression, it can be assigned directly — but every branch must evaluate to the same type, or it's a compile error.

Rustif_expression.rs
let condition = true;
let number = if condition { 5 } else { 6 };   // if is an EXPRESSION here
println!("{}", number);   // 5

// let bad = if condition { 5 } else { "six" };  // COMPILE ERROR: mismatched types

Three loops: loop, while, for

for is idiomatic for iterating a known range or collection; while loops on a condition; loop repeats forever until an explicit break.

Rustloops.rs
for i in 0..5 {              // idiomatic: range-based, exclusive of 5
    println!("{}", i);
}

let mut n = 0;
while n < 3 {                 // condition-based
    n += 1;
}

loop {                         // infinite until break
    println!("runs once");
    break;
}

A for loop's range is only as safe as the arithmetic that built it — that's the trap in the bug above. items.len() - 1 assumes there's at least one element; on an empty Vec, the subtraction underflows before the loop even starts. saturating_sub removes the assumption instead of hoping it always holds, clamping to 0 rather than wrapping around:

Rustunderflow_fix.rs
fn main() {
    let items: Vec<i32> = vec![];
    for i in 0..(items.len().saturating_sub(1)) {
        println!("{}", i);
    }
    println!("done");
}
// done

break can return a value from loop

This is a genuinely unusual feature: break inside a loop (only loop, not while or for) can carry a value, making the whole loop itself usable as an expression — a natural fit for retry logic that needs the successful result.

Rustloop_value.rs
let mut counter = 0;

let result = loop {
    counter += 1;
    if counter == 10 {
        break counter * 2;   // exits the loop AND produces a value
    }
};

println!("{}", result);   // 20

Sources

1
The Rust Project. The Rust Programming Language, "Control Flow", doc.rust-lang.org/book/ch03-05.