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