Do guard variables work the same way in every expression-form control-flow construct?
← Control Flow · Ref: Q1361
Yes. EK9 offers a UNIFORM guard mechanism across all six expression-form control-flow constructs: while, do/while, for-range, for-in, switch/given, and try. In every one, a guard in the header (<- declaration, ?= guarded assignment, or :=? assign-if-unset) is evaluated once at entry; if it leaves the guard variable unset the whole body is skipped and the expression yields the returning variable's initial value. The same three operators, the same skip-on-unset semantics, the same 'returning variable must be initialised' rule — everywhere.
THE SAME ?= GUARD, SIX CONSTRUCTS (each yields source() when set):
while: wResult <- while g ?= source() then loop ... do/while: dResult <- do g ?= source() ... while loop for-range: rResult <- for g ?= source() with i in 1 ... 1 ... for-in: fResult <- for g ?= source() with item in [2] ... switch: sResult <- switch g ?= source() with g ... try: tResult <- try g ?= source() ...
HEADER POSITION
The guard is always the pre-flow, written FIRST: after 'while'/'do'/'for'/'switch'/'try', before the construct's own control ('then'/'with' condition, range, collection, or trailing 'while'). It is never placed after the control.
IF IS THE EXCEPTION
'if' has no expression form — its value-producing counterpart is the ternary ('cond <- a : b'). Every OTHER construct has both statement and expression forms, and the guard behaves identically in both.
WHY UNIFORM
One rule to learn, no per-construct special cases, and the returning-variable-initialised requirement means a guard-skipped body can never yield an undefined value. This is enforced by the compiler (E08050).
See Q1354 (while), Q1355 (do-while), Q1356 (for-range), Q1357 (for-in), Q1359 (switch), Q1358 (try) for each construct in detail. See Q74 for the three guard operators. See Q1360 for the ternary (the if-expression equivalent).
Example
defines module qa.controlflow.guardsuniform defines function source() <- rtn as Integer: 7 defines program GuardsUniformDemo() stdout <- Stdout() // The SAME ?= guard, in each of the six expression-form constructs. // source() is set, so every body runs once and the result is 7. // The SAME ?= guard, with the SAME skip-on-unset semantics and the SAME // 'returning variable must be initialised' rule, shown in three representative // expression forms. source() is set, so each body runs once and yields 7. // (do-while, for-in and switch behave identically — see Q1355/Q1357/Q1359.) // while expression: guard, then 'then <control>' loopFlag <- true whileGuard as Integer? whileValue <- while whileGuard ?= source() then loopFlag <- rtn as Integer: -1 rtn: whileGuard loopFlag: false // for-range expression: guard, then 'with <var> in <range>' rangeGuard as Integer? rangeValue <- for rangeGuard ?= source() with i in 1 ... 1 <- rtn as Integer: -2 require i? rtn: rangeGuard // try expression: guard right after 'try' tryGuard as Integer? tryValue <- try tryGuard ?= source() <- rtn as Integer: -3 rtn: tryGuard stdout.println(`while=${whileValue} forRange=${rangeValue} try=${tryValue}`)
Common mistakes
E08050 — In every expression form the returning variable must be INITIALISED so a guard-skipped body still yields a defined value. Declaring it uninitialised ('<- rtn as Integer?') is rejected by E08050. See ek9 -h E08050.
Incorrect:
wResult <- while g ?= source() then loop <- rtn as Integer? rtn: g loop: false
Correct:
wResult <- while g ?= source() then loop <- rtn as Integer: -1 rtn: g loop: false
Other ways to ask this
- Is the guard syntax uniform across while, for, switch and try expressions?
- Can I use the same ?= guard in a while, for, switch and try that all return values?
- guards across all control flow expression forms
- one guard pattern for every value-producing construct
Coming from another language?
No mainstream language offers a single guard mechanism that is uniform across all loop, switch and try constructs AND lets each yield a value. Rust has 'while let'/'if let' but not for switch/try and they are statements; Swift has 'if let'/'guard let' but not across loops as value-producers. EK9 unifies declaration/guarded-assignment/assign-if-unset guards over while, do-while, for-range, for-in, switch and try, each with a value-producing expression form and a compiler-guaranteed initialised result.
Keywords: while, :=?, consistent, control, flow, uniform, returning, switch, ?=, do-while, expression, try, for, guard