What an infinite loop is
A loop repeats its body as long as its condition holds. If nothing inside the loop ever makes the condition false, the loop never ends and the program keeps running until it is stopped from outside. That is an infinite loop. It is nearly always a bug, with one important exception: a loop that is meant to run until something inside it decides to leave.
Rust has no C-style for (init; condition; update) loop. Its for loop walks over an iterator such as a range, and it ends by itself when the iterator runs out of values. The infinite loops you meet in Rust therefore come from while loops, whose condition and update you write yourself, or from loop, which has no condition at all.
How a while loop gets stuck
The lesson's exercise starts with this code, which should print the odd numbers from 15 down to 1:
let mut number = 15;
while number != 0 {
println!("{}", number);
number += 1;
}
Run it and it prints 15, 16, 17, 18 and keeps going. Two things are wrong, and each one alone would be enough to cause trouble.
- The variable moves the wrong way. The goal is to count down, but
number += 1makes it grow. Starting at 15 and growing,numbermoves away from 0 and can never equal it, sonumber != 0stays true. - The condition can never be reached. Even with
number -= 2, the values are 15, 13, 11, ..., 3, 1, -1. Subtracting 2 from an odd number never lands on 0, so the loop would step over the stopping value and carry on into negative numbers.
The second point is a common trap. A condition written with != only stops the loop if the variable hits that exact value. A condition such as number > 0 stops once the variable has passed the limit, so it is safer when the step is not 1.
Fixing it with a for loop over a range
A range with adapters describes the whole sequence in one place, so there is no variable to forget to update:
for number in (1..=15).rev().step_by(2) {
println!("{}", number);
}
This prints 15, 13, 11, 9, 7, 5, 3 and 1, each on its own line, and then stops. Read the pieces from left to right:
1..=15is an inclusive range: it contains 1, 2, ..., 15. The ordinary1..15would leave out 15..rev()reverses it, so the values come out as 15, 14, ..., 1..step_by(2)keeps the first value and then every second one after it, so 15, 13, 11, and so on.numberis created by theforitself. It needs nolet mutbefore the loop and no update inside it, and the body cannot change where the loop goes next.
The order of the adapters matters
step_by counts from the start of whatever it is given. After .rev() the sequence starts at 15, so every second value is odd. With a different top value, (1..=14).rev().step_by(2) prints 14, 12, 10, ..., 2: the same code produces even numbers, because the first value is now 14.
Writing (1..=15).step_by(2).rev() does not compile for an inclusive range of i32: the compiler reports that RangeInclusive<i32> does not implement ExactSizeIterator, which rev() needs after step_by here. Reverse first, then step.
Infinite loops on purpose
Sometimes you want a loop with no natural end, such as one that keeps reading input until it sees a stop signal. Rust has a dedicated keyword for it:
let mut k = 0;
loop {
k += 1;
if k * k > 50 {
break;
}
}
println!("{}", k);
This prints 8. loop repeats forever, and break is the only way out. Writing loop instead of while true tells both the reader and the compiler that the loop is meant to be endless, and the compiler knows that code after a loop with no break can never run.
Common mistakes
- Forgetting the update. A
whileloop that never changes the variable in its condition repeats forever. - Updating in the wrong direction. Counting down needs
-=, counting up needs+=. Check that every pass brings the variable closer to the stopping condition. - Using
!=with a step bigger than 1. The variable can jump over the target value and never match it. - Assuming a range is inclusive.
a..bstops beforeb;a..=bincludes it. - Looping over a vector by value.
for n in numsmoves the vector into the loop, sonumscan't be used afterwards. Usefor n in &numsto borrow it.
Key takeaways
- A loop is infinite when nothing inside it can make its condition false.
- Rust's
forloop iterates over an iterator, so it ends when the iterator is exhausted; the infinite loops arewhileloops with a bad update and deliberateloopblocks. - Prefer
<,>and their variants to!=as a stopping condition when the step isn't 1. (1..=15).rev().step_by(2)counts down from 15 to 1 by twos, with no variable to maintain.- Use
loopwithbreakwhen you want a loop that ends from inside.
