7.6break, continue, and loop labels
Lesson 7.4 used break on a promise that the full story would come later. It's later. This lesson covers the two jump statements that steer loops from the inside, plus the trick that's exclusive to loop and the tool that retired goto.
break
A break statement immediately ends the loop it appears in; execution continues at the first statement after the loop's block. It works in all three loops. The classic use is a sentinel, a special value that means "stop":
use std::io;
fn main() {
let mut sum = 0;
loop {
println!("Enter a number to add, or 0 to finish:");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("failed to read line");
let n: i32 = input.trim().parse().expect("please type a number");
if n == 0 {
break;
}
sum += n;
}
println!("The sum is: {sum}");
}Enter a number to add, or 0 to finish:
5
Enter a number to add, or 0 to finish:
12
Enter a number to add, or 0 to finish:
0
The sum is: 17
Don't confuse break with return. A break ends the loop; a return ends the whole function, loop and all (lesson 2.2). Inside main's loop the difference is invisible (there's nothing after the loop but the end of the program), so here it is somewhere it matters:
fn first_square_over(limit: i32) -> i32 {
let mut n = 1;
loop {
if n * n > limit {
return n; // exits first_square_over entirely
}
n += 1;
}
}
fn main() {
println!("{}", first_square_over(300));
}18
The return doesn't just stop the loop; it delivers the function's answer from the middle of it. (17² is 289, 18² is 324, so 18 it is.)
continue
A continue statement ends the current iteration, not the loop: execution jumps straight to the loop's next go-around. Use it to skip values that don't interest you:
fn main() {
for i in 1..=10 {
if i % 4 == 0 {
continue;
}
print!("{i} ");
}
println!();
}1 2 3 5 6 7 9 10
Multiples of 4 hit the continue, skip the print, and the for serves the next value. Like the early returns of lesson 7.2, a continue at the top of a loop body ("skip what I don't want, then proceed with clean cases") often reads better than wrapping the whole body in an if.
One trap, inherited from every language with continue. In a while loop, the update statement lives in the body, and continue jumps over the rest of the body:
fn main() {
let mut count = 0;
while count < 10 {
if count == 5 {
continue; // jumps to the condition check... without the += 1
}
print!("{count} ");
count += 1; // runs forever once count hits 5
}
}
This prints 0 1 2 3 4 and then sits forever: at 5, the continue skips the increment, so the condition re-checks 5 < 10 for eternity. New programmers meet this bug within a week of learning continue. Notice, though, what it requires: an update statement that lives below the continue. A for loop has no update statement in the body at all (the range does the advancing), so this entire bug is impossible there. One more point for for as your default loop.
Best practice
Use break and continue when they simplify a loop's logic, and prefer them to bool flag variables and extra nesting. (Some older style guides ban them as "hidden jumps"; modern practice, and this course, disagrees, for the same reason it likes early returns: handling the exceptional case immediately keeps the main path flat.)
break with a value: loop's exclusive
Here's lesson 7.4's promise, kept. Remember that loop is a promise to the compiler ("only a break gets me out of this"). The compiler repays it: a loop is an expression, and break can carry the loop's value out with it.
First, the version you'd write without it, using a flag variable:
fn main() {
let mut found = false;
let mut n = 1;
while !found {
if n * n > 300 {
found = true;
} else {
n += 1;
}
}
println!("first square over 300: {n} squared");
}
It works, but found is pure bureaucracy (one more mutable variable, one more thing the condition and the body must agree about). Now with break-with-value:
fn main() {
let mut n = 1;
let answer = loop {
if n * n > 300 {
break n;
}
n += 1;
};
println!("first square over 300: {answer} squared");
}first square over 300: 18 squared
break n ends the loop and makes n the value of the whole loop expression, which lands in answer. The flag is gone, and the loop now says what it's for: producing a value. (Note the ; after the closing brace: the loop is the right-hand side of a let statement, same as any expression.)
Why does only loop get this? Try it in a while and the compiler explains:
fn main() {
let mut n = 1;
let answer = while n < 100 {
n *= 2;
break n;
};
}error[E0571]: `break` with value from a `while` loop
--> src/main.rs:5:9
|
3 | let answer = while n < 100 {
| ------------- you can't `break` with a value in a `while` loop
4 | n *= 2;
5 | 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 - break n;
5 + break;
|
The logic: a while (or for) can end because its condition fails or its range runs dry, paths that pass through no break and therefore carry no value. What would answer be then? Rather than invent a default, Rust restricts break-with-value to loop, where every exit is a break and a value is always guaranteed. It's lesson 4.7's missing-else rule wearing a loop costume.
Lesson 7.4's menu now has its final form, and it's the idiom you'll actually see in the wild:
use std::io;
fn main() {
let choice = loop {
println!("Please pick an option (1-4):");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("failed to read line");
let n: u32 = input.trim().parse().expect("please type a number");
if n >= 1 && n <= 4 {
break n;
}
println!("{n} is not on the menu, try again.");
};
println!("you picked option {choice}");
}Please pick an option (1-4):
9
9 is not on the menu, try again.
Please pick an option (1-4):
3
you picked option 3
The loop is the validation: nothing escapes it but a valid choice, and choice doesn't even need to be mutable.
Loop labels: escaping nested loops
break and continue act on the innermost loop containing them. What if you're in a nested loop and want out of the outer one? This is the one job for which C++ programmers still reach for goto (their own textbooks admit as much, through gritted teeth). Rust deleted goto and kept a tool shaped exactly like this problem: name the loop, and tell break which name you mean.
fn main() {
'outer: for x in 1..=3 {
for y in 1..=3 {
if x * y == 6 {
println!("{x} * {y} is 6, calling off the whole search");
break 'outer;
}
println!("{x} * {y} = {}", x * y);
}
}
println!("done");
}1 * 1 = 1
1 * 2 = 2
1 * 3 = 3
2 * 1 = 2
2 * 2 = 4
2 * 3 is 6, calling off the whole search
done
A loop label is a name starting with a single quote, placed before the loop with a colon. break 'outer ends the loop named 'outer, both levels at once. A plain break in the same spot would only have ended the inner loop, and the search would have resumed at x = 3. continue takes labels too: continue 'outer abandons the inner loop and starts the outer loop's next iteration.
(That leading quote will look stranger before chapter 17 than after; it's the same marker Rust uses for another kind of name. For now: label names look like 'this.)
Quiz time
Question #1
What does this print?
fn main() {
for i in 1..=4 {
if i == 2 {
continue;
}
if i == 4 {
break;
}
print!("{i} ");
}
println!("done");
}Show solution
1 3 done
1 prints. 2 hits the continue and is skipped. 3 prints. 4 hits the break before its print, ending the loop. Execution lands on the println! after the loop.
Question #2
Rewrite this function to eliminate the flag variable, using a loop and break-with-value:
fn first_power_of_two_over(limit: u32) -> u32 {
let mut done = false;
let mut n = 1;
while !done {
if n > limit {
done = true;
} else {
n *= 2;
}
}
n
}Show solution
fn first_power_of_two_over(limit: u32) -> u32 {
let mut n = 1;
loop {
if n > limit {
break n;
}
n *= 2;
}
}
fn main() {
println!("{}", first_power_of_two_over(1000));
}1024
The loop is the function's tail expression, so the broken-out value is also the return value; done and the final bare n both disappear. (A return n instead of break n is also correct here, and a matter of taste.)
Question #3
Using nested for loops over 1..=9 each, find and print the first pair (a, b) whose product is divisible by 9, where a < b, then stop searching entirely. ("First" means: smallest a, then smallest b.) Use a labeled break.
Show solution
fn main() {
'search: for a in 1..=9 {
for b in 1..=9 {
if a < b && (a * b) % 9 == 0 {
println!("{a} * {b} = {}, divisible by 9", a * b);
break 'search;
}
}
}
}1 * 9 = 9, divisible by 9
The label is doing real work: a plain break would end only the inner loop, and the search would continue with a = 2, finding (and printing) more pairs. If you'd reached for a found flag checked by both loops instead, compare the two versions; the label says "stop the whole search" in one word.
Next: a kind of repetition that uses no loop at all. Functions that call themselves.