7.3 Loops
Rust provides three looping constructs: loop
, while
, and for
. Each serves different purposes, and they incorporate Rust’s emphasis on safety and expression-based evaluation. Notably, Rust does not have a direct equivalent to C’s do-while
loop.
7.3.1 The Infinite loop
The loop
keyword creates a loop that repeats indefinitely until explicitly stopped using break
.
fn main() { let mut counter = 0; loop { println!("Again!"); counter += 1; if counter == 3 { break; // Exit the loop } } }
loop
as an Expression: A unique feature of loop
is that break
can return a value from the loop, making loop
itself an expression. This is useful for retrying operations until they succeed.
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { // Pass the value back from the loop using break break counter * 2; } }; println!("The result is: {}", result); // Prints: The result is: 20 }
7.3.2 Conditional Loops: while
The while
loop executes its body as long as a condition remains true
. It checks the condition before each iteration.
fn main() { let mut number = 3; while number != 0 { println!("{}!", number); number -= 1; } println!("LIFTOFF!!!"); }
As with if
, the condition for while
must evaluate to a bool
. There’s no implicit conversion from integers.
Emulating do-while
: C’s do-while
loop executes the body at least once before checking the condition. You can achieve this in Rust using loop
with a conditional break
at the end:
fn main() { let mut i = 0; // Equivalent to C: do { ... } while (i < 5); loop { println!("Current i: {}", i); i += 1; if !(i < 5) { // Check condition at the end break; } } }
7.3.3 Iterator Loops: for
Rust’s for
loop is fundamentally different from C’s traditional three-part for
loop (for (init; condition; increment)
). Instead, Rust’s for
loop iterates over elements produced by an iterator. This is a safer and often more idiomatic way to handle sequences.
Iterating over a Range:
fn main() { // `0..5` is a range producing 0, 1, 2, 3, 4 (exclusive end) for i in 0..5 { println!("The number is: {}", i); } // `0..=5` is a range producing 0, 1, 2, 3, 4, 5 (inclusive end) for i in 0..=5 { println!("Inclusive range: {}", i); } }
Iterating over Collections (like Arrays):
fn main() { let a = [10, 20, 30, 40, 50]; // `a.iter()` creates an iterator over the elements of the array for element in a.iter() { println!("The value is: {}", element); } // Or more concisely, `for element in a` also works for arrays for element in a { println!("Again: {}", element); } }
Rust’s for
loop, by working with iterators, prevents common errors like off-by-one mistakes often associated with C-style index-based loops. We will discuss iterators in more detail later.
7.3.4 Controlling Loop Execution: break
and continue
Rust supports break
and continue
within all loop types (loop
, while
, for
), behaving similarly to their C counterparts:
break
: Immediately exits the innermost loop it’s contained within.- As noted earlier,
break
can optionally return a value only when used inside aloop
construct. When used insidewhile
orfor
,break
takes no arguments and the loop expression evaluates to()
.
- As noted earlier,
continue
: Skips the rest of the current loop iteration and proceeds to the next one. Forwhile
andfor
, this involves re-evaluating the condition or getting the next iterator element, respectively.
7.3.5 Labeled Loops for Nested Control
Sometimes you need to break
or continue
an outer loop from within an inner loop. C often requires goto
or boolean flags for this. Rust provides a cleaner mechanism using loop labels.
A label is defined using a single quote followed by an identifier (e.g., 'outer:
) placed before the loop statement. break
or continue
can then specify the label to target.
fn main() { let mut count = 0; 'outer: loop { // Label the outer loop println!("Entered the outer loop"); let mut remaining = 10; loop { // Inner loop (unlabeled) println!("remaining = {}", remaining); if remaining == 9 { // Breaks only the inner loop break; } if count == 2 { // Breaks the outer loop using the label break 'outer; } remaining -= 1; } count += 1; } println!("Exited outer loop. Count = {}", count); // Prints: Count = 2 }
fn main() { 'outer: for i in 0..3 { for j in 0..3 { if i == 1 && j == 1 { // Skip the rest of the 'outer loop's current iteration (i=1) // and proceed to the next iteration (i=2) continue 'outer; } println!("i = {}, j = {}", i, j); } } } // Output skips pairs where i is 1 after j reaches 1: // i = 0, j = 0 // i = 0, j = 1 // i = 0, j = 2 // i = 1, j = 0 // i = 2, j = 0 // i = 2, j = 1 // i = 2, j = 2
Labeled break
and continue
offer precise control over nested loop execution without resorting to less structured approaches like goto
.