How do I chain multiple guards for safe access?
← Data Flow Safety · Ref: Q636
When you need to access multiple values that might be unset, chain declaration guards by nesting if-guards or using sequential guards with explicit isSet checks.
NESTED GUARDS
Nest declaration guards to ensure all values are set:
if name <- findName(id) if score <- findScore(id) result: formatResult(name, score)
Each level guarantees its variable is set before the inner block executes.
SEQUENTIAL GUARDS
Use separate declaration guards in sequence when values are independent:
if name <- findName(id) stdout.println(name) if score <- findScore(id) stdout.println(score)
Each guard independently checks its value.
GUARD WITH ELSE
Provide fallback behavior when a guard fails:
if name <- findName(id) greet(name) else greetStranger()
WHY CHAIN GUARDS
Chaining guards ensures that every value used in a computation is guaranteed set. The compiler verifies each guard protects its variable scope. Without the guard, a lookup that returned nothing would flow on as an unset value into the computation (for Optional and Result values, unguarded get-style access is the compile-time error E08030).
See Q634 for guard basics. See Q163 for Optional unwrapping. See Q47 for Optional type patterns.
Example
defines module qa.dataflow.chainedguard defines constant USER_ALICE <- 1 USER_CHARLIE <- 3 UNKNOWN_USER <- 999 defines function findFirstName() -> userId as Integer <- rtn as String: String() if userId == USER_ALICE rtn: "Alice" else if userId == USER_CHARLIE rtn: "Charlie" findLastName() -> userId as Integer <- rtn as String: String() if userId == USER_ALICE rtn: "Smith" else if userId == USER_CHARLIE rtn: "Brown" findAge() -> userId as Integer <- rtn as Integer: Integer() if userId == USER_ALICE rtn: 30 else if userId == USER_CHARLIE rtn: 25 findDepartment() -> userId as Integer <- rtn as String: String() if userId == USER_ALICE rtn: "Engineering" defines program ChainedGuardDemo() stdout <- Stdout() //Nested guards: all must be set to build full profile if first <- findFirstName(USER_ALICE) if last <- findLastName(USER_ALICE) if age <- findAge(USER_ALICE) stdout.println(`${first} ${last}, age ${age}`) //Sequential independent guards if nameA <- findFirstName(USER_ALICE) stdout.println(`Found: ${nameA}`) if nameB <- findFirstName(UNKNOWN_USER) stdout.println(`Found: ${nameB}`) else stdout.println("User not found") //Guard with additional nested guard if dept <- findDepartment(USER_ALICE) if userName <- findFirstName(USER_ALICE) stdout.println(`${userName} works in ${dept}`) //Explicit isSet check pattern ageResult <- findAge(USER_CHARLIE) if ageResult? stdout.println(`Age found: ${ageResult}`)
Common mistakes
E50001 — Using first before its declaration guard is a forward reference error. The guard must be evaluated before using the variable. See ek9 -h E50001 for details.
Incorrect:
stdout.println(`${first} ${last}`) if first <- findFirstName(USER_ALICE)
Correct:
if first <- findFirstName(USER_ALICE) if last <- findLastName(USER_ALICE)
Other ways to ask this
- Can I nest guards in EK9?
- How do I safely access multiple potentially-unset values?
- What is the pattern for multiple optional lookups?
Coming from another language?
Java: nested if-present checks on Optional, pyramid of doom. Kotlin: safe call chaining ?. operator, scope functions let/run. Swift: if let chaining, optional binding. Rust: pattern matching with nested Some/None. Go: repeated nil checks. EK9: nested declaration guards 'if var <- expr()' with compiler-enforced scoping.
Keywords: E08040, optional, chain, E08030, access, guard, safe, multiple, data-flow, safety, initialize, scope, null-safe, nested, isset, absent