Why must I use a guard expression before accessing a returned value?
← Data Flow Safety · Ref: Q634
EK9 requires guard expressions to verify that a value is set before calling methods on it. Without the guard, the value might be unset, and calling a method on an unset value is unsafe.
GUARD EXPRESSION PATTERN
The guard pattern 'if value <- expression()' combines assignment with an isSet check. The variable is only in scope inside the guarded block, where it is guaranteed to be set. This eliminates unsafe access by construction.
WHY GUARDS ARE NEEDED
Functions can return unset values (e.g., an empty Optional, a failed lookup). Without a guard, calling methods on an unset value would be like calling methods on null in Java. EK9 prevents this at compile time rather than crashing at runtime.
MULTIPLE GUARD PATTERNS
Guards work identically in if, while, switch, for, and try statements. The same syntax 'var <- expr()' checks isSet and creates a scoped variable.
ALTERNATIVE: EXPLICIT isSet CHECK
You can also check with the ? operator: 'if someValue?' then use someValue inside the block. The compiler tracks that ? was checked.
See Q163 for Optional unwrapping patterns. See Q632 for definition ordering. See Q633 for branch initialization. See Q636 for chained guard access. See Q47 for Optional basics.
See Q687 for Result guard access. See Q688 for variable init order.
Example
defines module qa.dataflow.guardaccess defines constant ALICE_ID <- 1 BOB_ID <- 2 STUDENT_ALPHA <- 100 STUDENT_BETA <- 200 UNKNOWN_ID <- 999 defines function <?- Simulates a lookup that might return an unset value. -?> findUserName() -> userId as Integer <- userName as String: String() if userId == ALICE_ID userName: "Alice" else if userId == BOB_ID userName: "Bob" <?- Simulates a function that always returns a set value. -?> getDefaultGreeting() <- greeting as String: "Hello" <?- Simulates a numeric lookup that may return unset. -?> lookupScore() -> studentId as Integer <- score as Float: Float() if studentId == STUDENT_ALPHA score: 95.5 else if studentId == STUDENT_BETA score: 87.3 defines program GuardBeforeAccessDemo() stdout <- Stdout() //Guard pattern: variable only accessible when set if name <- findUserName(ALICE_ID) upperName <- name.upperCase() stdout.println(`Found user: ${upperName}`) //Guard on a function that may return unset if name2 <- findUserName(UNKNOWN_ID) stdout.println(`Found: ${name2}`) else stdout.println("User not found") //Multiple guards in sequence if score <- lookupScore(STUDENT_ALPHA) doubled <- score * 2.0 stdout.println(`Score doubled: ${doubled}`) //Explicit isSet check pattern greeting <- getDefaultGreeting() if greeting? stdout.println(greeting) //Nested guard: both must be set if userName <- findUserName(BOB_ID) if userScore <- lookupScore(STUDENT_BETA) stdout.println(`${userName} scored ${userScore}`)
Common mistakes
E50001 — Referencing name before its guard declaration is a forward reference error. The guard must come before any use. See ek9 -h E50001 for details.
Incorrect:
upperName <- name.upperCase() if name <- findUserName(ALICE_ID)
Correct:
if name <- findUserName(ALICE_ID) upperName <- name.upperCase()
Other ways to ask this
- How do guard expressions prevent unsafe access?
- What happens if I skip the guard on a function return?
- What is E08030 unsafe method access?
Coming from another language?
Java: no guard expressions, Optional.get() throws NoSuchElementException, nullable references crash with NPE. Kotlin: safe call ?. operator returns null on unset, Elvis ?: for defaults. Swift: if let/guard let unwraps optionals safely. Rust: if let Some(v) = expr pattern matching. Go: manual nil checks. EK9: guard expression 'if var <- expr()' enforced by compiler, variable only in scope when set.
Keywords: isSet, unset, method, migrate, null, guard, null-safe, access, optional, expression, absent, E08030, data-flow, E08040, initialize, safe, isset, safety