Why must I use a guard before accessing a returned value?
← Data Flow Safety · Ref: Q634
EK9 declaration guards let you act only on a value that is really set. Without the guard, the value might be unset, and the code would run on a missing value.
DECLARATION GUARD 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). EK9 has no null, so calling a method on an unset String or Integer does not crash: the result is simply unset too (tri-state propagation), and the guard is how you run code only when there is a real value. For Optional, Result and Iterator the compiler goes further: get-style access to the contents without a guard or isSet check is rejected at compile time (E08030).
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(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 declaration guard 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 guards 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 declaration guards, 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: declaration guard 'if var <- expr()' enforced by compiler, variable only in scope when set.
Keywords: expression, optional, access, null-safe, guard, null, migrate, method, unset, isSet, safety, isset, safe, initialize, E08040, data-flow, E08030, absent