Skip to content
Select theme

How to break out of two nested loops at once

Short answer: put a label on the outer loop and break with it. Write 'rows: for i in 0..grid.len() for the outer loop, then break 'rows; from inside the inner one. A plain break only leaves the loop it’s written in.

A plain break leaves the inner loop behind

Section titled “A plain break leaves the inner loop behind”

In src/main.rs:

cargo run prints:

checked 8 of 9 cells

Nine cells, one hit, and the search carries on anyway. The break ends the inner loop, so that row is abandoned after the 5, but the outer loop starts the next row and the scan runs to the end of the grid. Three cells were read after the value had already been found.

checked 5 of 9 cells

'rows is a label: the name goes in front of the loop keyword with a colon after it, and break 'rows ends whichever loop carries that label, however deep the break sits. The last row is never opened. Labels work the same way on while and loop.

row 0 is all positive
row 1 has a negative, skipping
row 2 is all positive

continue 'rows throws away the rest of the outer iteration and starts the next one, which is why row 1 never reaches the second println!. Row 2 printing is the proof that the label didn’t end the outer loop the way break does.

break can hand a value out of a loop, but only out of a loop:

cargo build prints:

error[E0571]: `break` with value from a `for` loop
--> src/main.rs:6:17
|
3 | 'rows: for i in 0..grid.len() {
| ----------------------------- you can't `break` with a value in a `for` loop
...
6 | break 'rows (i, j);
| ^^^^^^^^^^^^^^^^^^ can only break with a value inside `loop` or breakable block
|
help: use `break` on its own without a value inside this `for` loop
|
6 - break 'rows (i, j);
6 + break 'rows;
|
For more information about this error, try `rustc --explain E0571`.

The label isn’t what’s missing — a for loop has nowhere to put a value, and a while loop reports the same error. Wrap the two for loops in a labelled loop instead:

Some((1, 1))

Both break 'search lines leave both for loops at once, and the first one carries the position out. The None line is reached only when the loops finish without a match. A labelled block, 'label: { … }, is the other place a break can carry a value, and it isn’t a loop.

If the scan is a function of its own, skip the label:

Some((1, 1))
None

return leaves every loop between it and the end of the function, so both loops stay plain and neither needs a label. The not-found case is the tail expression, and it hands the caller an Option either way. Reach for the label version when the loops are already in the middle of a longer function and pulling them out into a function of their own isn’t worth it.