How do guard variables work in if statements?
← Control Flow · Ref: Q74
Guard variables let you combine variable creation with an isSet check in a single if statement. The guard ensures the if body only executes when the value is meaningful.
BASIC GUARD PATTERN
Use the declaration operator (<-) to create a guard variable:
if value <- getResult() stdout.println(value)
The variable 'value' is created and checked. If getResult() returns an unset value, the if body is skipped entirely. No exception, no crash.
GUARD WITH ADDITIONAL CONDITION
Use 'with' or 'then' to add a second condition after the guard:
if value <- getResult() with value > 10 stdout.println(value)
Both must be true: the guard must be SET and the extra condition must pass. Short-circuit applies: if the guard is unset, the condition is never evaluated.
IF/ELSE WITH GUARD
The else branch executes when the guard is unset:
if value <- getResult() process(value) else handleMissing()
THREE GUARD OPERATORS
EK9 supports three operators in if guards, each with different semantics:
1. Declaration (<-): Creates NEW variable, checks isSet of result if value <- getResult() 2. Assignment (:=): Assigns to EXISTING variable, NO isSet check (blind) if existing := getResult() with existing > 0 3. Guarded assignment (?=): Assigns to EXISTING variable, checks isSet of RHS if existing ?= getResult()
The declaration guard (<-) is by far the most common and recommended form.
SCOPING
Guard variables created with <- are scoped to the if/else block. They do not leak into the surrounding scope, preventing accidental reuse of one-time values.
WHAT 'SET' MEANS
The guard calls the type's isSet (?) operator. Each type defines what 'set' means: Integer is set when it has a numeric value, String when it has text content, Optional when it contains a value, collections when they have been properly initialized.
See Q29 for unset variables. See Q61 for basic if/else without guards. See Q75 for guards in switch. See Q76 for guards in for loops. See Q77 for guards in while loops. See Q78 for guards in try blocks. See Q166 for consistent safe pattern. See Q269 for input validation with guards. See Q272 for defense in depth with guards. See Q285 for multiple precondition validation with guards.
Example
defines module qa.flow.guard.ifstatement defines record Config host <- String() port <- Integer() default operator ? defines function getConfig() <- rtn <- Config() getActiveConfig() <- rtn <- Config() rtn.host: "localhost" rtn.port: 8080 defines program GuardIfDemo() stdout <- Stdout() // === BASIC GUARD: if value <- expr() === if config <- getActiveConfig() stdout.println("Got config: " + config.host) else stdout.println("No config available") // === GUARD WITH ADDITIONAL CONDITION === if config <- getActiveConfig() with config.port > 0 stdout.println("Config port: " + $config.port) // === GUARD WITH UNSET VALUE === if config <- getConfig() stdout.println("This should not print") else stdout.println("Config was unset, handled safely") // === ASSIGNMENT GUARD (no isSet check) === existing <- Config() if existing := getActiveConfig() with existing.port > 0 stdout.println("Assigned: " + existing.host) // === WHEN KEYWORD (alternative to if) === when config <- getActiveConfig() stdout.println("When guard works too: " + config.host)
Common mistakes
E01072 — EK9 has no return statement. Guard variables in if statements combine declaration and isSet checking in one expression, replacing the early-return-if-null pattern. See ek9 -h E01072 for details.
Incorrect:
config <- getActiveConfig() if not config? return stdout.println("Got config: " + config.host)
Correct:
if config <- getActiveConfig() stdout.println("Got config: " + config.host) else stdout.println("No config available")
Other ways to ask this
- How do I combine variable declaration with an if condition?
- What is the if guard pattern in EK9?
- How does EK9 handle null-safe if checks?
- How do I use the declaration operator in an if statement?
Coming from another language?
Java: No guard syntax. Must write: 'var v = getValue(); if (v != null) { use(v); }' as two separate steps. Variable leaks into outer scope. Optional requires: 'getValue().ifPresent(v -> use(v))' which forces a lambda. Python: No guard syntax. Walrus operator (:=) in 3.8+ comes close: 'if (v := getValue()) is not None:' but only handles None, not general isSet semantics. Rust: 'if let Some(v) = get_value()' for Option destructuring. Close to EK9 guards but limited to pattern matching. Go: 'if v := getValue(); v != nil' allows init statement in if but uses nil checks, no isSet concept. Kotlin: 'val v = getValue(); if (v != null)' with smart cast. No single-expression guard. Swift: 'if let v = getValue()' for optional binding, closest to EK9 but limited to Optional type. EK9: 'if value <- getResult()' combines declaration, assignment, and isSet check in one expression. Works with any type that has the ? operator, not just Optional. Three operator variants for different semantics.
Keywords: declaration, branch, operator, null-safe, isset, control, condition, unset, flow, guard, null, safe, if, scope, check