CEP-0070: Value-Producing Loop Expressions¶
Draft
Draft proposal for loops that can produce a value through break payloads. V1 loops have success type void, and break has no payload.
Summary¶
Catalyst should eventually decide whether loop expressions can produce values, and whether that feature should use existing while / for loops or a dedicated endless loop { ... } form.
The promising direction is to introduce loop { ... } as an unconditional loop expression. A loop has no natural completion path, so any value it produces must come from a break payload. That keeps the rule simple: break value is to loop what return value is to a function.
Motivation¶
Expression-oriented code sometimes wants to compute a value by repeatedly doing work until a result is found:
const token = {
var result: ?Token = null
while not result {
const next = scanner.next()
if next.is_token() {
result = next
}
}
result.unwrap()
}
A value-producing loop can express this without sentinel state whose only purpose is to carry the result out of the loop.
Proposed Direction¶
Add a dedicated endless loop expression:
const token = loop {
const next = scanner.next()
if next.is_token() {
break next
}
}
The proposed break payload forms are:
break value: exit the nearest value-producing loop and make that loop producevalue;break :label value: exit the labeled value-producing loop and make that loop producevalue, if labeled loop control is accepted.
A loop with no break payloads keeps success type void. A loop with one or more break payloads has a success type determined from the payload expressions that target that loop. The same compatibility model used for block and conditional expression values should apply: payloads must be compatible with the loop's expected type or with each other when no expected type is available.
Why loop¶
Using while or for as value-producing expressions makes natural completion part of the type rule. A finite for may exhaust its source, and a while condition may stop when the condition becomes false. If that exit path is reachable, the loop has no value to produce.
loop avoids that ambiguity because it has no condition and no iterator exhaustion edge. A loop either exits through break, diverges forever, or exits through another diverging expression such as return or error propagation.
This proposal should prefer loop unless a later design adds an explicit fallback result form for conditional or iterator loops.
Rejected or Deferred Shape¶
Value-producing while and for without a fallback are rejected by this proposal direction:
const found = for item in items {
if matches(item) {
break item
}
}
The programmer should write an explicit local fallback:
var found: ?Item = null
for item in items {
if matches(item) {
found = item
break
}
}
A future proposal could add a fallback spelling for natural completion, such as a loop else branch, but that is separate from the core loop direction.
Cleanup and Ownership¶
For break value, the payload expression should be evaluated before exit cleanup begins, then transferred into the target loop's result slot using the same copy, move, borrow, and resource rules as an explicit block or return value. Cleanup must not dispose a value that has been moved into the loop result.
If labeled loop control is accepted, break :label value crosses all inner loops and blocks between the break site and the labeled loop. It must run pending defer statements, resource cleanup, and compiler-owned loop temporary cleanup in the same order as other early exits.
Existing CEP Overlap¶
This proposal owns break payloads, value-producing loop typing, and the possible loop keyword.
It does not own labeled control targets. Those are tracked by CEP-0069: Labeled Loop Control.
It also does not own value-producing local declarations. CEP-0068: Value-Producing Local Declarations asks whether a declaration statement can complete with a value. This proposal asks whether a loop can complete with a value supplied by break.
V1 Compatibility¶
V1 remains unchanged:
loopis not a loop form;breakdoes not accept a payload;whileandforhave success typevoid;- code that needs a loop result uses an explicit local variable or extracts a helper function.
Accepting this proposal would reserve or contextualize loop and would relax programs that currently fail because they use break payloads. If loop becomes a full reserved keyword, migration diagnostics should identify existing bindings named loop.
Open Questions¶
- Should
loopbe a reserved keyword or a contextual keyword before{? - Should
break valuebe rejected in a loop whose expected result isvoidrather than silently discarding the payload? - Should a value-producing
looprequire an expected type, or can payloads infer a result type without context? - Should conditional or iterator loops ever gain a fallback result form, or should
loopremain the only value-producing loop expression? - How should diagnostics explain a
loopwhose body has no reachable value break and no obvious diverging behavior?