RFA-606 · Case file with fixtures · Case 578 of 694 · Compiler evidence
Rust break Inside a Labelled Block Must Name Its Destination
Labelled blocks are value-producing early-exit scopes. Name whether break exits the block or an enclosing loop so refactors cannot redirect control silently.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all Rust targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- The bare exit could mean leaving the new one-shot block or crossing it to terminate an outer loop, so Rust requires the destination.
- First discriminating check
- Name the block or enclosing loop by semantic role and use an explicit labelled break, preferably returning a typed outcome.
A labelled block creates its own valid break destination. Inside that block, a bare break must not silently skip it and target an outer loop. The failing fixture uses this ambiguous shape and receives E0695.
Labelled blocks are not loops
Syntax such as 'decision: { ... } executes once. It can end early with break 'decision value, making the block a structured alternative to deeply nested conditional logic. It cannot repeat or accept continue.
The official E0695 explanation requires the break to name whether it targets the labelled block or an enclosing labelled loop. Rust refuses to infer the outer loop for an unlabeled break across this boundary.
This prevents a local block insertion from changing the visible nesting while leaving a bare break's intention unclear.
The repair returns a value from the block
The repaired fixture uses break 'decision "ready" and assigns the block result to outcome. The label names the operation and the value removes the need for a mutable placeholder outside.
If the original intent were to exit the outer loop, I would label that loop and write break 'records or another semantic name. Both repairs compile, but they produce different control flow. This is why the diagnostic cannot choose for me.
The Reference describes labelled block expressions and the requirement for explicit labelled breaks within them.
Semantic label names survive refactoring
Labels such as 'outer and 'inner describe current layout. When another loop is inserted, they become misleading. I name operations: 'parse_header, 'select_route, 'retry, or 'records.
Then each break reads like an outcome. A labelled block often fits validation pipelines where several checks can produce a fallback value. A loop label fits repetition or search.
I keep labelled regions short. If many exits carry different result types or side effects, a named function returning Result or enum usually communicates better and supports direct tests.
Break values must share one type
Every exit from a value-producing block must be compatible with the block's result type, including its tail expression. This lets Rust type-check the assignment regardless of which path runs.
I use Result when early exits represent errors with context, and Option when absence is normal. A string sentinel like the fixture is only for a small demonstration. Typed outcomes prevent different failure reasons from collapsing into one magic value.
Side effects before a break still occur. The compiler ensures legal targeting, not transactional rollback. If a block mutates state and later chooses a fallback, tests should verify partial progress or the code should stage changes before commit.
Nested control flow deserves boundary tests
For an outer record loop containing a labelled parse block, I test a successful record, a block-level fallback, and a condition that ends the entire record loop. Inputs should make these outcomes observably different.
I also check cleanup. Values leaving scope are dropped along each break path. Guards and locks can make drop timing operationally important even when control flow is valid.
For observability, I emit metrics after classifying the typed outcome rather than immediately before every break. Central reporting prevents one newly added exit from silently skipping counters, and it keeps the labelled block focused on decision rather than duplicated side effects.
The break expression rules define which constructs accept values. while and for do not produce arbitrary break values in the same way as loop and labelled blocks.
My E0695 checklist
- Is the bare break lexically inside a labelled block?
- Should it exit that block or a separately labelled enclosing loop?
- Does the label describe the operation rather than nesting position?
- Can a break value replace mutable result state?
- Do all exits and the tail expression have compatible types?
- Would Result, Option, or an enum preserve outcome meaning?
- What state changes and drops occur before each exit?
- Are tests distinguishing local fallback from outer-loop termination?
The core principle is that early exit needs a named semantic boundary when a labelled block intervenes. E0695 prevents indentation from deciding meaning. I make the destination and returned outcome explicit so the control flow remains stable under refactoring.