Why this matters
Computers are useful because they repeat work tirelessly: process every line of a file, retry a connection, iterate a simulation until it settles. Rust has three loop forms, loop, while and for. This lesson covers the first two and the keywords that steer any loop; the next lesson is about for, the one you will use most for walking over data.
loop: repeat forever, until break
fn main() {
let mut n = 1;
loop {
println!("n = {n}");
n *= 2;
if n > 20 {
break;
}
}
println!("stopped at {n}");
}
n = 1
n = 2
n = 4
n = 8
n = 16
stopped at 32
loop repeats its body forever. break leaves the loop immediately and continues after it. Use loop when the exit condition is in the middle of the body or when there are several exits, or for programs that genuinely run forever (servers, event loops).
break with a value
Like if, loop is an expression. break value; ends the loop and makes value the result:
fn main() {
let mut x: u64 = 1;
let mut tries = 0;
let first_big = loop {
x = x * 7 % 1000;
tries += 1;
if x > 900 {
break x;
}
};
println!("{first_big} after {tries} tries");
}
943 after 15 tries
This is the idiomatic way to "search until found and return the result". (Only loop can break with a value; a while loop might end because its condition became false, and then there would be no value to give.)
while: repeat while a condition holds
fn main() {
let mut n = 1234;
let mut digits = 0;
let mut sum = 0;
while n > 0 {
sum += n % 10;
n /= 10;
digits += 1;
}
println!("{digits} digits, digit sum {sum}");
}
4 digits, digit sum 10
while cond { body } checks the condition before each pass. If it is false at the start, the body never runs. Something in the body must eventually make it false, or the loop never ends.
Underneath, both loops are just jumps:
while n > 0 { body } loop { body }
TOP: TOP:
if !(n > 0) jump END body
body jump TOP
jump TOP (break = jump END)
END: END:
continue: skip to the next pass
continue abandons the rest of the current pass and starts the next one (re-checking the condition, in a while):
fn main() {
let mut n = 0;
while n < 10 {
n += 1;
if n % 3 != 0 {
continue;
}
println!("{n} is a multiple of 3");
}
}
3 is a multiple of 3
6 is a multiple of 3
9 is a multiple of 3
continue and break are best used to handle special cases at the top of a loop body, keeping the main work unindented: "skip comments", "stop at the end marker". Notice that n += 1 comes before the continue. Had it been at the bottom, continue would skip it and the loop would spin forever on the same n.
Nested loops and labels
break and continue apply to the innermost loop. To control an outer loop, give it a label, an identifier starting with an apostrophe:
fn main() {
let target = 42;
'search: for a in 1..10 {
for b in 1..10 {
if a * b == target {
println!("found {a} * {b}");
break 'search;
}
}
}
}
found 6 * 7
(for a in 1..10 counts a from 1 to 9; the next lesson explains it.) Without the label, break would only end the inner loop and the search would continue with the next a.
Thinking about loops
Two questions make loops correct:
- Does it end? Identify what changes each pass and why it must eventually stop the loop. In the digit-sum loop,
nshrinks by a factor of ten each time and must reach 0. - What is true at the top of every pass? This is called the loop invariant. In the digit-sum loop:
sumis the sum of the digits already removed fromn. When the loop ends withn == 0, the invariant tells yousumis the sum of all digits. Thinking this way catches off-by-one errors before you run anything.
Common mistakes
Infinite loops. Forgetting to update the variable in the condition, or skipping the update with continue. In the exercises, a run that does not finish within 10 seconds is reported as timed out. In a terminal, Ctrl-C stops a runaway program.
Forgetting mut on the loop variable: E0384, cannot assign twice to an immutable variable.
Wanting a value from while:
fn main() {
let mut n = 0;
let x = while n < 5 {
n += 1;
if n == 3 { break n; }
};
}
error[E0571]: `break` with value from a `while` loop
--> main.rs:5:21
|
3 | let x = while n < 5 {
| ----------- you can't `break` with a value in a `while` loop
4 | n += 1;
5 | if n == 3 { break n; }
| ^^^^^^^ can only break with a value inside `loop` or breakable block
|
help: use `break` on its own without a value inside this `while` loop
|
5 | if n == 3 { break; }
| ~~~~~
E0571. Use loop with an explicit condition check when you need a result.
Off-by-one boundaries. while n > 0 versus while n >= 0: with an unsigned n, the second one never becomes false (and decrementing past 0 panics). Trace the first and last pass by hand.
Recap
loop { }repeats untilbreak;break valuemakes the loop an expression with that value.while cond { }checks before each pass and may run zero times.continueskips to the next pass;breakleaves the loop.- Labels (
'outer: loop) letbreak 'outerandcontinue 'outertarget an outer loop. - Every loop needs a reason to end; state what is true at the top of each pass.
Going further: compute the greatest common divisor of two numbers with Euclid's algorithm (while b != 0 { let t = b; b = a % b; a = t; }) and convince yourself why it terminates.