What is the difference between :=, ?=, and :=? guard operators in EK9?

← Control Flow · Ref: Q1023

EK9 has three assignment guard operators with distinct semantics for EXISTING variables. Each checks something different before proceeding with the control flow body.

:= (BLIND ASSIGNMENT)

Always evaluates expression. Always assigns. No safety checks.

  switch mainValue := getValue()
    case 1 ...

Use when: Value is known safe, or an explicit 'with' condition validates it.

?= (GUARDED ASSIGNMENT - checks RIGHT side)
Always evaluates expression. Checks if result is SET. Only assigns if SET.

  switch mainValue ?= getValue()
    case 1 ...

Use when: Expression might return unset (network call, lookup, parse). Protects against assigning bad data.

:=? (ASSIGNMENT IF UNSET - checks LEFT side)
First checks if target is already set. Only evaluates expression if target is UNSET. Then checks result before assigning.

  switch mainValue :=? getValue()
    case 1 ...

Use when: Variable may already have a value (caching, defaults, priority chains). Avoids re-evaluation.

DECISION TREE:

  First time using this variable? -> Use <- (declaration guard)
  Variable exists, keep if already set? -> Use :=? (lazy, avoids re-evaluation)
  Variable exists, need to validate new value? -> Use ?= (checks result quality)
  Variable exists, value known safe? -> Use := (blind, no checks)

See Q74 for declaration guard (<-). See Q79 for :=? standalone usage. See Q759 for ?= as Boolean expression. See Q1024 for ?= across control flow. See Q1025 for := vs :=? across control flow.

Example

defines module qa.controlflow.guard.operator.contrast

  defines function

    getZero()
      <- rtn as Integer?
      rtn: 0

    getUnsetValue()
      <- rtn as Integer?
      rtn: Integer()

    getOne()
      <- rtn as Integer?
      rtn: 1

  defines program

    GuardOperatorContrastDemo()
      stdout <- Stdout()

      // === := BLIND ASSIGNMENT ===
      // Always assigns. getZero() returns 0, matches no case -> default.
      blindResult <- Integer()

      switch blindResult := getZero()
        case 1
          stdout.println(":= matched case 1")
        default
          stdout.println(`:= hit default (value: ${blindResult})`)

      // === ?= GUARDED ASSIGNMENT (checks RIGHT side) ===
      // Evaluates getUnsetValue() which returns an unset Integer.
      // Because result is NOT set, assignment is SKIPPED and switch body is SKIPPED.
      guardedResult <- Integer()

      switch guardedResult ?= getUnsetValue()
        case 1
          stdout.println("?= matched case 1")
        default
          stdout.println("?= hit default")

      if guardedResult?
        stdout.println("?= assigned a value")
      else
        stdout.println("?= skipped (RHS unset, no assignment)")

      // === :=? ASSIGNMENT IF UNSET (checks LEFT side) ===
      // lazyResult starts unset, so :=? WILL evaluate getOne().
      // getOne() returns 1, which is set, so assignment happens and matches case 1.
      lazyResult <- Integer()

      switch lazyResult :=? getOne()
        case 1
          stdout.println(`:=? assigned (value: ${lazyResult})`)
        default
          stdout.println(":=? hit default")

Common mistakes

E01073 — EK9 has no null; use the tri-state '?' isSet operator (or a guard) instead of comparing to null. See ek9 -h E01073 for details.

Incorrect:

if guardedResult <> null

Correct:

if guardedResult?
Other ways to ask this
  • When should I use := vs ?= vs :=? in a switch statement?
  • How do the three assignment guard operators differ in EK9?
  • What guard operator should I use in EK9 control flow?
  • Show me all three guard operators side by side in EK9

Coming from another language?

Java: No equivalent. Must manually write if/else chains for each pattern. Optional.orElse() handles one level. Kotlin: Elvis (?:) handles null-only cases, not tri-state isSet. Rust: if let handles pattern matching, no assignment-if-unset. Go: if err := f(); err != nil — closest to := but no ?= or :=? equivalents. EK9: Three operators cover declaration, blind assignment, quality-checked assignment, and lazy assignment — all in one unified syntax across all control flow.

Keywords: control, switch, blind, contrast, operator, decision, lazy, flow, choose, difference, assignment, guarded, guard, unset, comparison