A loop is a control structure that repeats code as long as a condition is met or there are remaining elements to traverse.
If control flow (if/else) is the brain that makes decisions, loops are the muscle that does the heavy work. Repeating a task thousands of times is what computers were invented for.
Rust offers us three types of loops to handle repetition: loop, while, and for.
If you come from C or Java, you’ll feel comfortable with while and for, but loop might seem strange to you. Also, Rust’s for is not the typical “initialize; check; increment” of C, but a much safer modern iterator.
The Tireless One: loop
The loop keyword creates an infinite loop. It simply executes the code block over and over until we explicitly stop it or the program exits.
fn main() {
loop {
println!("This will never stop!");
// If you ran this, you'd have to press Ctrl+C to kill it
}
}What good is a loop that never ends? It is very useful in situations where we don’t know how many times we need to iterate, such as:
- A web server listening for requests.
- The “Game Loop” of a video game.
- A thread processing messages in the background.
Controlling the Loop with break and continue
To exit an infinite loop (or any other type), we use:
break: Immediately breaks the loop and exits.continue: Skips the rest of the current iteration’s code and jumps back to the beginning of the loop for the next round.
fn main() {
let mut counter = 0;
loop {
counter += 1;
if counter == 3 {
println!("Skipping 3");
continue; // Goes back up, does not print the rest
}
println!("Counter: {}", counter);
if counter == 5 {
println!("Enough!");
break; // Exit the loop
}
}
}loop as an Expression
This is quite specific to Rust. Since loop is an expression, we can use it to calculate a value and return it via break.
Imagine a loop that searches for a number and, when it finds it, assigns it to a variable.
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // Returns 20
}
}; // <-- Note the semicolon here, we are assigning to 'result'
println!("The result is: {}", result);
}This avoids having to declare a mutable variable outside the loop and modify it inside.
The Conditional One: while
The while loop is the classic “while the condition holds true”. It evaluates a boolean expression; if it is true, it executes the code. If it is false, it exits.
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!");
}Avoid using while to iterate over collections when you don’t need the index.
In C++ it’s common to use while with an index (i) to traverse an array.
In Rust, this pattern is more error-prone and can introduce bounds checks that an iterator avoids or helps optimize. To traverse elements directly, we use for.
The King of Loops: for
The for loop is one of the most used structures in Rust. It does not iterate over a condition, but over any value that implements IntoIterator, such as a collection or a range. It is safe, fast, and concise.
Iterating Over Ranges
If you want to execute a code a specific number of times, we use the Range syntax.
a..b: Range fromatob(exclusive, does not include b).a..=b: Range fromatob(inclusive, includes b).
fn main() {
// From 1 to 3 (4 is not included)
println!("Normal count:");
for i in 1..4 {
println!("{}", i);
}
// From 1 to 4 (inclusive)
println!("Inclusive count:");
for i in 1..=4 {
println!("{}", i);
}
}What if we want to go backwards? Ranges in Rust do not automatically detect if the second number is smaller. We must use the .rev() (reverse) method.
// Countdown: 4, 3, 2, 1
for i in (1..5).rev() {
println!("{}...", i);
}Iterating Over Collections
The best way to traverse an Array is using for. Rust handles giving us each element without the risk of going out of memory bounds.
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("The value is: {}", element);
}
}This is what we call a zero-cost abstraction. Iterators are designed to compile without adding overhead compared to an equivalent manual traversal, but they avoid many errors with indices.
Loop Labels
Sometimes we have nested loops (a loop inside another) and we want to do a break that exits both, not just the inner one.
By default, break breaks the nearest loop. To specify which one we want to break, we can give the loop a label. Labels in Rust start with a single quote '.
fn main() {
let mut counter = 0;
'outer_loop: loop {
println!("Entering the outer loop");
'inner_loop: loop {
println!("Entering the inner loop");
// Logic...
if counter == 2 {
break 'outer_loop; // We exit EVERYTHING!
}
counter += 1;
break; // This only breaks 'inner_loop
}
}
println!("End of program");
}It is a rarely used feature, but tremendously useful when you need it (for example, searching for a value in a two-dimensional matrix).