How do guards work in EK9 if statements?
← Control Flow · Ref: Q971
Guards combine variable handling with isSet checking in a single statement. The block only executes if the guarded variable is SET.
THE GUARD FORMS:
1. DECLARATION GUARD (<-):
if name <- getName() stdout.println(name)
Declares name; the block runs only if it is SET.
2. RE-POINT AND CHECK (:= then x?):
selectedItem <- String() if selectedItem := findItem("key") then selectedItem? stdout.println(selectedItem)
Always assigns the result to the existing variable (an unset result included); the explicit 'selectedItem?' then runs the block only if it is SET.
3. ASSIGN IF UNSET (:=?):
config <- loadDefaults() if config :=? loadOverrides() stdout.println(config)
':=?' tests the LEFT side: it assigns (and only then calls loadOverrides()) when config is currently UNSET. Here config is already set by loadDefaults(), so it is kept, loadOverrides() is not called, and the block runs with the defaults.
PLAIN ASSIGNMENT (:=) CHECKS NOTHING
':=' in a control-flow head is a blind assignment: no isSet check. So 'if x := expr()' on its own has no condition and is rejected (E07340); add one with 'with' or 'then', or use a guard. In 'switch v := expr()' the switch runs whatever the value is, and an unset value goes to default.
SCOPE AND ORDER
A '<-' guard DECLARES a new variable that belongs to the if/else chain: the else branches can read it, and after the chain it is gone (naming it there is E50001). ':=' and ':=?' assign an EXISTING variable, so the value is still there after the chain - and ':=' on a name nothing declared is E50001, because ':=' never declares. Each pre-flow runs once, when its block is REACHED: in 'else if v <- next()' next() is called only after the earlier conditions were false, never when an earlier branch ran.
WITH ADDITIONAL CONDITION:
if name <- getName() then name.length() > minLength stdout.println(name)
Runs only if name is SET AND the condition holds.
The same guard forms work in every control-flow statement:
if v <- expr() declaration guard switch v <- expr() declaration guard in switch while v <- expr() with condition the guard runs ONCE, before the loop for v <- expr() then i in items the guard runs ONCE, before the loop try v <- expr() an unset guard skips the whole try
while and for always need their own condition or loop after 'with'/'then': the guard is an entry gate that runs once, and only that condition is re-checked on each iteration.
Bridge: Like Go's 'if err := doSomething(); err != nil'. Like Rust's 'if let Some(v) = expr()'. Like Swift's 'if let v = optionalExpr'.
See Q74 for more guard examples. See Q75 for guard switch. See Q77 for while guards. See Q1023 for the guard operators side by side.
Example
defines module qa.controlflow.guardif defines function <?- Returns a greeting if the name has content, or unset if empty. -?> findGreeting() as pure -> personName as String <- rtn as String: String() if personName? rtn: "Hello, " + personName defines program GuardIfDemo() stdout <- Stdout() // BASIC GUARD: only enters if findGreeting returns SET if greeting <- findGreeting("Steve") stdout.println(greeting) // GUARD WITH UNSET: block is skipped if greeting <- findGreeting(String()) stdout.println("This won't print") else stdout.println("Greeting was unset - skipped") // GUARD WITH CONDITION: SET and passes additional test minLength <- 3 if greeting <- findGreeting("Alice") then greeting.length() > minLength stdout.println(`Long greeting: ${greeting}`) // ASSIGN IF UNSET (:=?): the target is already set, so it is kept chosen <- findGreeting("Dana") if chosen :=? findGreeting("Eve") stdout.println(`Kept: ${chosen}`) // GUARD IN SWITCH: declares and checks in one step switch greet <- findGreeting("Charlie") with greet case == "Hello, Charlie" stdout.println("Found Charlie's greeting") default stdout.println(`Other greeting: ${greet}`)
Common mistakes
E01073 — 'null' does not exist in EK9 — use tri-state semantics (unset/set) with declaration guards such as 'if name <- expr()' instead of comparing against null. See ek9 -h E01073 for details.
Incorrect:
greeting.length() > minLength and greeting <> null
Correct:
greeting.length() > minLength
Other ways to ask this
- What is a guard in EK9?
- How does if v <- expr() work in EK9?
- How do I combine declaration with null checking in EK9?
- What does the <- guard do inside an if statement?
Coming from another language?
Go: if err := f(); err != nil { } — same concept. Rust: if let Some(v) = expr { } — same concept. Swift: if let v = optional { } — same concept. Java: requires separate null check. EK9 guards work in if, switch, while, for and try; in while and for the guard runs once, before the loop; ':=?' assigns only when the target is unset.
Keywords: null safety, guard, declaration guard, control flow, isSet check