How do guards work in a do/while loop, in both statement and expression form?

← Control Flow · Ref: Q1355

The do/while loop accepts a guard in its header (after 'do'), and — like the other loops — it has an expression form that yields a value. The guard is checked once at entry; if it leaves the guard variable unset the body is skipped, even though a do/while normally runs its body at least once.

DO/WHILE STATEMENT WITH A ?= GUARD

  first as String?
  more <- true
  do first ?= fetchRecord()
    stdout.println(`got ${first}`)
    more: false
  while more

The guarded assignment (?=) assigns fetchRecord() to 'first' and checks it is SET before the body runs.

DO/WHILE EXPRESSION WITH A :=? GUARD

The expression form declares a returning variable as the first body line and yields its final value:

  cached as String?
  loopOnce <- true
  summary <- do cached :=? fetchRecord()
    <- rtn as String: "none"
    rtn: cached
    loopOnce: false
  while loopOnce

Assign-if-unset (:=?) only assigns when 'cached' is currently unset. If the guard leaves it unset, the body is skipped and 'summary' is the initial value "none".

GUARD POSITION

The guard is the pre-flow, written directly after 'do'; the loop control stays where it belongs, after the body in the trailing 'while' clause.

See Q67 for the plain do/while. See Q77 for while guards. See Q1354 for the while expression form with a guard. See Q1361 for guards used uniformly across all expression-form constructs.

Example

defines module qa.controlflow.guarddowhile

  defines function

    fetchRecord()
      <- rtn as String: "record-A"

  defines program

    GuardDoWhileDemo()
      stdout <- Stdout()

      // do/while STATEMENT with a ?= guard (guard checked once, after 'do')
      first as String?
      more <- true
      do first ?= fetchRecord()
        stdout.println(`got ${first}`)
        more: false
      while more

      // do/while EXPRESSION with a :=? assign-if-unset guard
      cached as String?
      loopOnce <- true
      summary <- do cached :=? fetchRecord()
        <- rtn as String: "none"
        rtn: cached
        loopOnce: false
      while loopOnce
      stdout.println(`summary is ${summary}`)

Common mistakes

E02001 — The guard belongs in the header, directly after 'do' (do first ?= fetchRecord()), not as the first statement of the body. As a guard it can skip the whole body when unset. See ek9 -h E02001.

Incorrect:

do
        first ?= fetchRecord()
        stdout.println(`got ${first}`)
      while more

Correct:

do first ?= fetchRecord()
        stdout.println(`got ${first}`)
        more: false
      while more
Other ways to ask this
  • Can a do-while loop use a guard variable?
  • How do I use ?= or :=? in a do/while loop?
  • do-while as an expression with a guard
  • guarded assignment in a do while loop

Coming from another language?

Java: do-while is a statement only; it always runs the body once and cannot gate the first iteration on a guarded, loop-scoped variable, nor produce a value. You would use a nullable variable plus a manual check. Kotlin/Swift/Rust: do-while (or repeat-while) is likewise a statement with no guard binding and no value form. EK9: do/while accepts <-, ?= and :=? guards that can skip even the first iteration when unset, and has an expression form that yields the returning variable.

Keywords: returning, isset, flow, while, guarded, :=?, control, expression, ?=, loop, do-while, do, guard