Control Flow

Loop Labels

A loop label (or block label) is an identifier prefixed with a single quote ('label) attached to a loop, while, for loop, or plain block { ... }. Passing a loop label to break 'label or continue 'label lets you control a specific outer loop directly from inside deeply nested inner loops.

Loop Label Syntax Rules

A loop label is written immediately before the loop, while, for, or { keyword, followed by a colon (:):

'outer_loop: for row in 0..10 {
───────────  ───
     │        └── Loop or block being labeled
     └─────────── Label declaration (`'` + snake_case identifier + `:`)
    for col in 0..10 {
        if done {
            break 'outer_loop; // References `'outer_loop` (no colon here!)
        }
    }
}
Rule Details
Leading character Always starts with a single quote/tick ('), just like lifetime names
Naming convention Lowercase snake_case (such as 'search, 'rows, 'outer)
Reserved names Cannot be 'static or '_
Declaration vs Use Declared with a trailing colon ('outer: loop { ... }), referenced without a colon (break 'outer;)

Breaking and Continuing an Outer Loop

By default, unlabeled break and continue apply only to the innermost loop. With labels, you can target any enclosing loop:

fn main() {
    'rows: for r in 1..=3 {
        for c in 1..=3 {
            if r == 2 && c == 2 {
                println!("  [r={r}, c={c}] -> `continue 'rows` (skipping rest of row 2)");
                continue 'rows;
            }
            if r == 3 && c == 2 {
                println!("  [r={r}, c={c}] -> `break 'rows` (exiting both loops completely)");
                break 'rows;
            }
            println!("  Visiting r={r}, c={c}");
        }
    }
    println!("Finished grid traversal.");
}
  Visiting r=1, c=1
  Visiting r=1, c=2
  Visiting r=1, c=3
  Visiting r=2, c=1
  [r=2, c=2] -> `continue 'rows` (skipping rest of row 2)
  Visiting r=3, c=1
  [r=3, c=2] -> `break 'rows` (exiting both loops completely)
Finished grid traversal.
'rows: for r in 1..=3 {           <-- Target of `continue 'rows` (advances `r`)
    for c in 1..=3 {
        if r == 2 && c == 2 {
            continue 'rows; ------+
        }
        if r == 3 && c == 2 {
            break 'rows; ---------+--+
        }                            |
    }                                |
}                                    |
println!("Finished..."); <-----------+-- Target of `break 'rows`

Returning a Value from a Labeled loop (break 'label value)

When an outer loop is labeled, you can pass both the label and a return value to break (break 'label expr;) from inside an inner loop:

fn find_target_in_3d(cube: &[[[i32; 2]; 2]; 2], target: i32) -> Option<(usize, usize, usize)> {
    let mut z = 0;

    'search: loop {
        if z >= cube.len() {
            break 'search None;
        }
        for y in 0..cube[z].len() {
            for x in 0..cube[z][y].len() {
                if cube[z][y][x] == target {
                    // Exits the two `for` loops AND the outer `'search` loop with `Some(...)`!
                    break 'search Some((z, y, x));
                }
            }
        }
        z += 1;
    }
}

fn main() {
    let data = [
        [[1, 2], [3, 4]],
        [[5, 6], [7, 8]],
    ];
    println!("Coordinates of 7: {:?}", find_target_in_3d(&data, 7));
}
Coordinates of 7: Some((1, 1, 0))

Labeled Plain Blocks ('block: { ... })

Rust also allows attaching a label to a plain block expression ('label: { ... }) that is not a loop at all! Inside a labeled block:

  • break 'label; or break 'label value; is valid and jumps directly to the end of the labeled block (producing value as the block's result).
  • continue 'label; is forbidden (E0696), because a plain block only runs once and does not loop.

Labeled blocks are useful when a multi-step calculation inside a function needs an early exit path that should only leave the block rather than returning from the entire function:

fn main() {
    let input = "  -15 ";

    let parsed_positive: u32 = 'parse_step: {
        let trimmed = input.trim();
        let Ok(num) = trimmed.parse::<i32>() else {
            break 'parse_step 0;
        };

        if num < 0 {
            println!("Clamping negative number {num} to 0");
            break 'parse_step 0;
        }

        num as u32
    };

    println!("parsed_positive = {parsed_positive}");
}
Clamping negative number -15 to 0
parsed_positive = 0

Common Errors and Lints

Referencing an Undeclared or Out-of-Scope Label (E0426)

A label is only in scope inside the body of the loop or block it labels. Referencing a label after its loop has closed or misspelling a label name produces E0426:

// ❌ does not compile
fn main() {
    'outer: for i in 0..3 {
        for j in 0..3 {
            if i + j > 3 {
                break 'outre;
            }
        }
    }
}
error[E0426]: use of undeclared label `'outre`
 --> src/main.rs:5:23
  |
5 |                 break 'outre;
  |                       ^^^^^^ undeclared label `'outre`
  |
help: a label with a similar name exists in this scope
  |
5 |                 break 'outer;
  |                       ~~~~~~

error: aborting due to 1 previous error

Unused Label Warning (unused_labels)

If you attach a 'label: to a loop or block but never reference 'label in any break or continue statement, rustc emits an unused_labels warning:

fn main() {
    'unneeded: for i in 0..2 {
        println!("{i}");
    }
}
warning: unused label
 --> src/main.rs:2:5
  |
2 |     'unneeded: for i in 0..2 {
  |     ^^^^^^^^^^
  |
  = note: `#[warn(unused_labels)]` on by default

Shadowing an Outer Label (shadow_reuse / Compiler Warning)

Using the exact same 'label name on both an outer loop and a nested inner loop causes the inner label to shadow the outer one, making the outer loop unreachable by label name and triggering a compiler warning (label name 'a shadows a label name that is already in scope). Always give nested labeled loops distinct, descriptive names such as 'rows and 'cols.