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