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
| Construct | Repeats | Produces Value | Use Case |
|---|---|---|---|
loop | Forever until break | Yes, via break value | Unknown iteration count |
while | While condition true | () | Condition known in advance |
while let | While pattern matches | () | Draining Option/Result |
for | Over iterator | () | Iterating collections |
Loop Control
| Statement | Effect |
|---|---|
break | Exit the loop |
break value | Exit and produce value (loop only) |
continue | Skip to next iteration |
break 'label | Exit labeled loop |
continue 'label | Continue labeled loop |
break 'label value | Exit labeled loop with value |
For Loop Iteration Forms
| Form | Consumes | Allows Mutation |
|---|---|---|
for x in collection | Yes | No |
for x in &collection | No | No |
for x in &mut collection | No | Yes |
Range Syntax
| Range | Meaning |
|---|---|
0..5 | 0, 1, 2, 3, 4 (exclusive) |
0..=5 | 0, 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
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Collection moved | for x in vec consumes | Use &vec for reference |
| Infinite loop | No break in loop | Add exit condition |
| Off-by-one in range | 0..n excludes n | Use 0..=n for inclusive |
| Cannot modify | Iterating by immutable ref | Use &mut |
| Outer loop continues | break only exits inner | Use labeled break |
| Bounds check in loop | Index-based iteration | Use iterator form |
while let pattern never matches | Wrong pattern | Check 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
| Item | Value |
|---|---|
loop | Repeats until break; can produce value via break value |
while | Repeats while condition is true; type () |
while let | Repeats while pattern matches |
for | Iterates over IntoIterator; type () |
break | Exits the loop |
break value | Exits and produces value (loop only) |
continue | Skips to next iteration |
| Loop label | 'name: before loop |
break 'name | Exits labeled loop |
| Ranges | 0..5 exclusive, 0..=5 inclusive |
| Iterator adapters | map, filter, take, enumerate, rev, step_by |
Key takeaways:
loopis for infinite loops with explicit exit. It is clearer thanwhile truebecause the infinite nature is the point, and the exit condition is in the body.loopcan produce a value.break valueexits the loop and makes the loop expression produce that value. This is unique toloop;whileandforalways produce().whileis for condition-based repetition. The condition is checked before each iteration, and the loop exits when it becomes false.while letdrains an iterator or collection. It loops as long as the pattern matches, which is the idiomatic way to consumeOptionvalues frompopor similar methods.foris the idiomatic way to iterate. It works with anyIntoIterator, eliminates bounds checks, and is less error-prone than index-based loops.- Iteration can consume, borrow, or mutably borrow.
for x in collectionconsumes;for x in &collectionborrows;for x in &mut collectionmutably borrows. Choose based on whether the collection is needed after the loop. - Loop labels allow breaking out of nested loops. Without a label,
breakexits only the innermost loop. With'outer:,break 'outerexits the outer loop. - Iterator adapters compose with
for.filter,map,take,enumerate,rev, andstep_bycan 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!