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:
fn main() { let grid = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]; let mut checked = 0; for i in 0..grid.len() { for j in 0..grid[i].len() { checked += 1; if grid[i][j] == 5 { break; } } } println!("checked {checked} of 9 cells");}cargo run prints:
checked 8 of 9 cellsNine 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.
Label the outer loop
Section titled “Label the outer loop”fn main() { let grid = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]; let mut checked = 0; 'rows: for i in 0..grid.len() { for j in 0..grid[i].len() { checked += 1; if grid[i][j] == 5 { break 'rows; } } } println!("checked {checked} of 9 cells");}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.
continue takes the same label
Section titled “continue takes the same label”fn main() { let grid = vec![vec![1, 2, 3], vec![4, -5, 6], vec![7, 8, 9]]; 'rows: for i in 0..grid.len() { for j in 0..grid[i].len() { if grid[i][j] < 0 { println!("row {i} has a negative, skipping"); continue 'rows; } } println!("row {i} is all positive"); }}row 0 is all positiverow 1 has a negative, skippingrow 2 is all positivecontinue '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.
Breaking with a value
Section titled “Breaking with a value”break can hand a value out of a loop, but only out of a loop:
fn main() { let grid = vec![vec![1, 2, 3], vec![4, 5, 6]]; 'rows: for i in 0..grid.len() { for j in 0..grid[i].len() { if grid[i][j] == 5 { break 'rows (i, j); } } }}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:
fn main() { let grid = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]; let position = 'search: loop { for i in 0..grid.len() { for j in 0..grid[i].len() { if grid[i][j] == 5 { break 'search Some((i, j)); } } } break 'search None; }; println!("{position:?}");}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.
Or let return do it
Section titled “Or let return do it”If the scan is a function of its own, skip the label:
fn find_position(grid: &[Vec<i32>], target: i32) -> Option<(usize, usize)> { for i in 0..grid.len() { for j in 0..grid[i].len() { if grid[i][j] == target { return Some((i, j)); } } } None}
fn main() { let grid = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]; println!("{:?}", find_position(&grid, 5)); println!("{:?}", find_position(&grid, 12));}Some((1, 1))Nonereturn 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.
Related
Section titled “Related”- How to use the range syntax — the article before this one, and where
0..grid[i].len()comes from - Why do both branches of an if need the same type? — the expression/value split behind
break Some(…) - The bits that trip everyone up — the rest of this chapter
- Next in this chapter: iterating over an array