Skip to content

CEP-0069: Labeled Loop Control

Draft

Draft proposal for Zig-inspired labels on loops plus labeled break and continue. V1 loops remain unlabeled, and break / continue target only the nearest enclosing loop.

Summary

Catalyst should eventually support labels on while and for loops, break :label to exit an enclosing labeled loop, and continue :label to start the next iteration of an enclosing labeled loop.

Also, yes: nice.

This proposal is about control targets only. Break payloads and value-producing loops are tracked separately by CEP-0070: Value-Producing Loop Expressions.

Motivation

Nested loops often need to exit an outer search loop as soon as a result is found:

var found: ?Item = null

for row in rows {
  if found {
    break
  }

  for item in row {
    if matches(item) {
      found = item
      break
    }
  }
}

Without labeled exits, the programmer must add sentinel state, duplicate conditions, extract a helper function, or use a less direct control-flow shape. A labeled break lets the source say which loop is complete.

Labeled continue is the corresponding multi-level skip operation. It lets an inner block finish the current iteration of an enclosing loop without encoding that decision as mutable state.

Proposed Direction

Future loop labels should prefix a while or for expression:

search: for row in rows {
  for item in row {
    if matches(item) {
      break :search
    }
  }
}

The proposed control forms are:

  • break: exit the nearest enclosing loop;
  • break :label: exit the nearest enclosing loop with that label;
  • continue: start the next iteration of the nearest enclosing loop;
  • continue :label: start the next iteration of the nearest enclosing loop with that label.

Loop labels are control-flow labels, not value names. They should live in their own label namespace, be visible only inside the labeled loop body, and resolve to the nearest enclosing loop with that label. Diagnostics should reject a label reference when no enclosing loop has that label.

Example

Possible future shape:

rows: for row in rows {
  for item in row {
    if item.skip_row() {
      continue :rows
    }

    if matches(item) {
      break :rows
    }
  }
}

This example is illustrative only. V1 rejects loop labels and labeled break / continue.

Cleanup and Ownership

A labeled break exits every lexical scope between the break expression and the target loop. It must run pending defer statements, resource cleanup, and compiler-owned loop temporary cleanup in the same order as other early exits.

A labeled continue exits every lexical scope between the continue expression and the target loop body scope, runs the cleanup for the current target-loop iteration, then begins the target loop's next iteration. Hidden iterator and receiver temporaries owned by the target loop itself are not disposed by continue :label; they remain live for the next iteration just as they do for an ordinary nearest-loop continue.

For both forms, hidden iterator and receiver temporaries belonging to inner loops are cleaned as those inner loops are exited.

Existing CEP Overlap

This proposal owns loop labels and labeled control targets.

It deliberately does not own reassignable loop bindings. That remains CEP-0057: Reassignable Loop Bindings.

It also does not own break payloads or loop result typing. Those are tracked by CEP-0070: Value-Producing Loop Expressions.

V1 Compatibility

V1 remains unchanged:

  • while and for loops do not accept labels;
  • break exits the nearest enclosing loop only;
  • continue starts the next iteration of the nearest enclosing loop only;
  • break and continue do not accept target labels;
  • every loop form has success type void.

Accepting this proposal would be a source-compatible relaxation for programs that currently fail to parse or fail semantic validation because they use loop labels or labeled loop control. It may require new diagnostics for unresolved labels, duplicate labels, non-loop labels, and cleanup-sensitive exits across resource-owning scopes.

Open Questions

  • Should duplicate nested labels be rejected, or should the nearest enclosing label shadow outer labels?
  • Should labels be available on a future loop expression if CEP-0070 adds one?
  • Should label names share syntax exactly with ordinary identifiers, or use a distinct spelling?