| |

Rust 10 🦀 Loops in Rust: loop, while, and for

Rust provides three loop constructs: loop, while, and for. Each serves a different purpose, and the choice among them is not arbitrary. loop repeats forever until an explicit break exits, and it can return a value from the break. while repeats as long as a condition is true. for iterates over the elements of an iterator, which is the idiomatic way to traverse a collection. The for loop is the most common in Rust code because iterators are pervasive, and because for eliminates the off-by-one errors and bounds checks that plague index-based loops.

What distinguishes Rust’s loops from those of other languages is that they are expressions. A loop with a break value produces that value, so the loop can be assigned or returned. The while and for loops have type (), but they still participate in the expression model. Loop labels let you break or continue an outer loop from an inner one, which is a feature that statement-oriented languages lack. And the iterator protocol that for uses is the foundation of Rust’s approach to collections, so understanding for is understanding how Rust code traverses data.

This chapter covers loop with break and continue, while and while let, for and the iterator protocol, loop labels, nested loops, the difference between for and index-based iteration, and the patterns that make iteration idiomatic.

Key point: loop repeats until break, and break value makes the loop produce that value. while repeats while a condition is true. for iterates over anything that implements IntoIterator, which is the idiomatic way to traverse collections. Loop labels ('outer:) allow break and continue to target outer loops.


Why three loop constructs exist

The infinite-loop problem. Some loops do not have a condition known in advance. A server loop, an event loop, or a retry loop runs until an explicit exit condition is reached. The loop construct expresses this directly: it repeats forever, and the break statement is the only way out. This is clearer than while true, which obscures the intent by making the infinite nature look like a condition.

The condition-loop problem. Other loops repeat while a condition holds: reading until EOF, processing while a queue is non-empty, retrying while a flag is set. The while construct expresses this directly. The condition is checked before each iteration, and the loop exits when the condition becomes false.

The iteration problem. The most common loop in Rust is iterating over a collection: a vector, a slice, a range, or a map. The for loop expresses this directly, and it works with any type that implements IntoIterator. This is the idiomatic form because it eliminates the index variable, the bounds check, and the off-by-one errors that come with manual indexing.

The value-returning problem. A loop sometimes needs to compute a value. The loop construct allows break value, which exits the loop and makes the loop expression produce that value. This is the only loop that can produce a value this way; while and for always have type ().

The control-flow problem. Inside nested loops, it is sometimes necessary to break out of an outer loop from within an inner one. Without labels, this requires a flag variable and a check after the inner loop. Rust’s loop labels solve this directly: break 'outer; exits the labeled loop.


a. The loop construct

loop repeats its body forever until a break statement exits. It is the most primitive loop.

let mut counter = 0;

loop {
    counter += 1;
    if counter == 5 {
        break;
    }
}

A break can carry a value, and the loop expression produces that value:

let mut counter = 0;

let result = loop {
    counter += 1;
    if counter == 5 {
        break counter * 2;
    }
};

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

This pattern is used when a value is computed inside the loop and only known when the exit condition is reached. The break value is the mechanism for returning it.

continue skips the rest of the current iteration and starts the next:

let mut n = 0;

loop {
    n += 1;
    if n % 2 == 0 {
        continue;   // skip even numbers
    }
    if n > 10 {
        break;
    }
    println!("{}", n);   // 1, 3, 5, 7, 9
}

The continue jumps back to the top of the loop, and the condition is not re-checked (there is no condition in a loop). The break exits.


b. The while construct

while repeats as long as a condition is true. The condition is checked before each iteration.

let mut n = 0;

while n < 5 {
    println!("{}", n);
    n += 1;
}

If the condition is false initially, the body never executes:

let mut n = 10;

while n < 5 {
    println!("{}", n);   // never runs
    n += 1;
}

The while loop is appropriate when the condition is known in advance and is not based on an iterator. Counting loops, retry loops, and flag-based loops use while.

A while loop can be combined with break and continue:

let mut n = 0;

while n < 10 {
    n += 1;
    if n % 2 == 0 {
        continue;
    }
    if n > 7 {
        break;
    }
    println!("{}", n);   // 1, 3, 5, 7
}

while let is a variant that loops while a pattern matches, which is useful with Option and Result:

let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop() {
    println!("{}", top);   // 3, 2, 1
}

The loop continues as long as stack.pop() returns Some. When it returns None, the pattern does not match, and the loop exits. This is the idiomatic way to drain a collection.


c. The for construct

for iterates over anything that implements IntoIterator. This includes ranges, vectors, slices, arrays, maps, and any custom type that implements the trait.

for i in 0..5 {
    println!("{}", i);   // 0, 1, 2, 3, 4
}

The range 0..5 is exclusive of the upper bound. The range 0..=5 is inclusive:

for i in 0..=5 {
    println!("{}", i);   // 0, 1, 2, 3, 4, 5
}

Iterating over a vector by reference is the common case:

let numbers = vec![10, 20, 30];

for n in &numbers {
    println!("{}", n);   // 10, 20, 30
}

Iterating by value consumes the collection, which is fine when it is no longer needed:

let numbers = vec![10, 20, 30];

for n in numbers {
    println!("{}", n);
}
// numbers is moved; cannot be used here

Iterating by mutable reference allows modification:

let mut numbers = vec![1, 2, 3];

for n in &mut numbers {
    *n *= 2;
}
// numbers is [2, 4, 6]

The for loop uses the iterator protocol: it calls into_iter() on the collection, then repeatedly calls next() until it returns None. This uniform interface means any iterator can be used in a for loop, including iterators produced by map, filter, take, and other adapters.

for n in (1..10).filter(|x| x % 2 == 0).take(3) {
    println!("{}", n);   // 2, 4, 6
}

The for loop is the idiomatic way to traverse a collection. Manual indexing is discouraged because it requires a bounds check and is prone to off-by-one errors:

// Discouraged
let numbers = vec![10, 20, 30];
for i in 0..numbers.len() {
    println!("{}", numbers[i]);   // bounds check each iteration
}

// Preferred
for n in &numbers {
    println!("{}", n);            // no bounds check
}

d. Loop labels

A loop can be labeled with a lifetime-like syntax: 'label:. The label allows break and continue to target a specific loop, which matters with nested loops.

'outer: for i in 0..3 {
    for j in 0..3 {
        if i == 1 && j == 1 {
            break 'outer;   // exits both loops
        }
        println!("({}, {})", i, j);
    }
}

Without the label, break would exit only the inner loop, and the outer loop would continue. The label 'outer makes the intent explicit.

A labeled continue skips the rest of the inner iteration and continues the outer loop:

'outer: for i in 0..3 {
    for j in 0..3 {
        if j == 1 {
            continue 'outer;
        }
        println!("({}, {})", i, j);
    }
}

Labels are useful for searching nested structures, breaking out of a matrix traversal, and any situation where the exit condition spans multiple loop levels.

Labeled loops can also carry values when they are loop loops:

let result = 'search: loop {
    for i in 0..10 {
        for j in 0..10 {
            if i * j == 42 {
                break 'search (i, j);
            }
        }
    }
};

The break 'search (i, j) exits both loops and makes the outer loop produce the tuple.


Complete Example Session

// ============================================
// PART 1: BASIC LOOP WITH BREAK
// ============================================
fn main() {
    let mut counter = 0;

    loop {
        counter += 1;
        if counter == 5 {
            break;
        }
    }

    println!("{}", counter);   // 5
}
// ============================================
// PART 2: LOOP WITH BREAK VALUE
// ============================================
fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;
        if counter == 5 {
            break counter * 2;
        }
    };

    println!("{}", result);   // 10
}
// ============================================
// PART 3: CONTINUE IN LOOP
// ============================================
fn main() {
    let mut n = 0;

    loop {
        n += 1;
        if n > 10 {
            break;
        }
        if n % 2 == 0 {
            continue;
        }
        println!("{}", n);   // 1, 3, 5, 7, 9
    }
}
// ============================================
// PART 4: WHILE LOOP
// ============================================
fn main() {
    let mut n = 0;

    while n < 5 {
        println!("{}", n);   // 0, 1, 2, 3, 4
        n += 1;
    }
}
// ============================================
// PART 5: WHILE LET
// ============================================
fn main() {
    let mut stack = vec![1, 2, 3];

    while let Some(top) = stack.pop() {
        println!("{}", top);   // 3, 2, 1
    }
}
// ============================================
// PART 6: FOR OVER RANGE
// ============================================
fn main() {
    for i in 0..5 {
        println!("{}", i);   // 0, 1, 2, 3, 4
    }

    for i in 1..=3 {
        println!("{}", i);   // 1, 2, 3
    }
}
// ============================================
// PART 7: FOR OVER COLLECTION
// ============================================
fn main() {
    let numbers = vec![10, 20, 30];

    for n in &numbers {
        println!("{}", n);
    }
}
// ============================================
// PART 8: FOR WITH MUTABLE REFERENCE
// ============================================
fn main() {
    let mut numbers = vec![1, 2, 3];

    for n in &mut numbers {
        *n *= 2;
    }

    println!("{:?}", numbers);   // [2, 4, 6]
}
// ============================================
// PART 9: LABELED BREAK
// ============================================
fn main() {
    'outer: for i in 0..3 {
        for j in 0..3 {
            if i == 1 && j == 1 {
                break 'outer;
            }
            println!("({}, {})", i, j);
        }
    }
    // (0, 0), (0, 1), (0, 2), (1, 0)
}
// ============================================
// PART 10: LABELED LOOP WITH VALUE
// ============================================
fn main() {
    let result = 'search: loop {
        for i in 0..10 {
            for j in 0..10 {
                if i * j == 42 {
                    break 'search (i, j);
                }
            }
        }
    };

    println!("{:?}", result);   // (6, 7) or (7, 6)
}

These ten parts cover loop with break, break value, continue, while, while let, for over ranges, for over collections, for with mutable references, labeled break, and labeled loops with values.


Quick Reference

Loop Constructs

ConstructRepeatsProduces ValueUse Case
loopForever until breakYes, via break valueUnknown iteration count
whileWhile condition true()Condition known in advance
while letWhile pattern matches()Draining Option/Result
forOver iterator()Iterating collections

Loop Control

StatementEffect
breakExit the loop
break valueExit and produce value (loop only)
continueSkip to next iteration
break 'labelExit labeled loop
continue 'labelContinue labeled loop
break 'label valueExit labeled loop with value

For Loop Iteration Forms

FormConsumesAllows Mutation
for x in collectionYesNo
for x in &collectionNoNo
for x in &mut collectionNoYes

Range Syntax

RangeMeaning
0..50, 1, 2, 3, 4 (exclusive)
0..=50, 1, 2, 3, 4, 5 (inclusive)
(0..5).rev()4, 3, 2, 1, 0
(0..10).step_by(2)0, 2, 4, 6, 8

Best Practices

✅ Do This:

for item in &items { }              // Iterate by reference
for (i, item) in items.iter().enumerate() { }  // With index
let result = loop { break compute(); };        // Loop with value
while let Some(x) = iter.next() { }            // Drain iterator
'outer: for i in 0..n { for j in 0..n { break 'outer; } }

❌ Don’t Do This:

for i in 0..items.len() { items[i] }   // ❌ Index-based; use iterator
while true { }                          // ❌ Use loop
loop { break; }                         // ❌ Use if for single check
for x in collection { }                 // ❌ Consumes if not needed

Common Pitfalls

PitfallWhy It HappensFix
Collection movedfor x in vec consumesUse &vec for reference
Infinite loopNo break in loopAdd exit condition
Off-by-one in range0..n excludes nUse 0..=n for inclusive
Cannot modifyIterating by immutable refUse &mut
Outer loop continuesbreak only exits innerUse labeled break
Bounds check in loopIndex-based iterationUse iterator form
while let pattern never matchesWrong patternCheck the pattern against the value

Real-World Examples

1. Retry Loop

let mut attempts = 0;
let result = loop {
    attempts += 1;
    match try_operation() {
        Ok(value) => break Ok(value),
        Err(_) if attempts < 3 => continue,
        Err(e) => break Err(e),
    }
};

2. Read Until EOF

let mut buffer = String::new();
while reader.read_line(&mut buffer)? > 0 {
    process(&buffer);
}

3. Drain a Stack

while let Some(item) = stack.pop() {
    process(item);
}

4. Iterate with Index

for (i, item) in items.iter().enumerate() {
    println!("{}: {}", i, item);
}

5. Modify in Place

for score in &mut scores {
    *score = (*score).max(0);
}

6. Nested Search with Label

'search: for row in &matrix {
    for cell in row {
        if *cell == target {
            break 'search;
        }
    }
}

7. Reverse Iteration

for i in (0..5).rev() {
    println!("{}", i);   // 4, 3, 2, 1, 0
}

8. Step by Two

for i in (0..10).step_by(2) {
    println!("{}", i);   // 0, 2, 4, 6, 8
}

9. Filter and Take

for n in (1..100).filter(|x| x % 7 == 0).take(5) {
    println!("{}", n);   // 7, 14, 21, 28, 35
}

10. Loop with Value

let sum = loop {
    let n = next_number();
    if n == 0 {
        break sum_so_far;
    }
    sum_so_far += n;
};

Visual

Three Loop Constructs

┌──────────────────────────────────────────────────────────────┐
│  loop                        while                           │
│  ┌────────────────────┐      ┌────────────────────┐          │
│  │ loop {             │      │ while cond {       │          │
│  │   // body          │      │   // body          │          │
│  │   if done { break }│      │ }                  │          │
│  │ }                  │      │                    │          │
│  └────────────────────┘      └────────────────────┘          │
│  Repeats forever             Repeats while condition         │
│  until break.                is true.                        │
│                                                              │
│  for                                                         │
│  ┌────────────────────┐                                      │
│  │ for x in iterator {│                                      │
│  │   // body          │                                      │
│  │ }                  │                                      │
│  └────────────────────┘                                      │
│  Iterates over any IntoIterator.                             │
└──────────────────────────────────────────────────────────────┘

Loop with Break Value

┌──────────────────────────────────────────────────────────────┐
│  let result = loop {                                         │
│      let input = read();                                     │
│      if input == "quit" {                                    │
│          break 0;         ──▶ loop produces 0                │
│      }                                                       │
│      if let Ok(n) = input.parse::<i32>() {                   │
│          break n;         ──▶ loop produces n                │
│      }                                                       │
│  };                                                          │
│                                                              │
│  result holds the value from the break.                      │
└──────────────────────────────────────────────────────────────┘

For Loop Iteration Forms

┌──────────────────────────────────────────────────────────────┐
│  for x in collection     → consumes, x is owned              │
│  for x in &collection    → borrows, x is &T                  │
│  for x in &mut collection → mutably borrows, x is &mut T     │
│                                                              │
│  let v = vec![1, 2, 3];                                      │
│                                                              │
│  for x in &v { }         → v still usable after loop         │
│  for x in &mut v { }     → v still usable after loop         │
│  for x in v { }          → v moved; cannot use after loop    │
└──────────────────────────────────────────────────────────────┘

Labeled Break in Nested Loops

┌──────────────────────────────────────────────────────────────┐
│  'outer: for i in 0..3 {                                     │
│      for j in 0..3 {                                         │
│          if condition {                                      │
│              break 'outer;   ──▶ exits both loops            │
│          }                                                   │
│      }                                                       │
│  }                                                           │
│                                                              │
│  Without the label, break exits only the inner loop,         │
│  and the outer loop continues.                               │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
loopRepeats until break; can produce value via break value
whileRepeats while condition is true; type ()
while letRepeats while pattern matches
forIterates over IntoIterator; type ()
breakExits the loop
break valueExits and produces value (loop only)
continueSkips to next iteration
Loop label'name: before loop
break 'nameExits labeled loop
Ranges0..5 exclusive, 0..=5 inclusive
Iterator adaptersmap, filter, take, enumerate, rev, step_by

Key takeaways:

  • loop is for infinite loops with explicit exit. It is clearer than while true because the infinite nature is the point, and the exit condition is in the body.
  • loop can produce a value. break value exits the loop and makes the loop expression produce that value. This is unique to loop; while and for always produce ().
  • while is for condition-based repetition. The condition is checked before each iteration, and the loop exits when it becomes false.
  • while let drains an iterator or collection. It loops as long as the pattern matches, which is the idiomatic way to consume Option values from pop or similar methods.
  • for is the idiomatic way to iterate. It works with any IntoIterator, eliminates bounds checks, and is less error-prone than index-based loops.
  • Iteration can consume, borrow, or mutably borrow. for x in collection consumes; for x in &collection borrows; for x in &mut collection mutably borrows. Choose based on whether the collection is needed after the loop.
  • Loop labels allow breaking out of nested loops. Without a label, break exits only the innermost loop. With 'outer:, break 'outer exits the outer loop.
  • Iterator adapters compose with for. filter, map, take, enumerate, rev, and step_by can be chained to express complex iteration without manual index manipulation.

Remember: Rust has three loop constructs because there are three distinct looping needs. loop handles the case where the exit condition is discovered inside the body and a value may be produced. while handles the case where a condition determines whether to continue. for handles the common case of iterating over a collection, and it is the form you should reach for most of the time. The iterator protocol that for uses is one of Rust’s strengths: any type that implements IntoIterator can be traversed, and adapters like map and filter compose to express complex traversal without manual state. Loop labels solve the nested-loop exit problem cleanly. When you write a loop in Rust, start with for, use while when a condition is the driver, and use loop when the exit is discovered in the body or when the loop must produce a value.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!