How do I chain multiple guard expressions for safe access?
← Data Flow Safety · Ref: Q636
When you need to access multiple values that might be unset, chain guard expressions by nesting if-guards or using sequential guards with explicit isSet checks.
NESTED GUARDS
Nest guard expressions 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 guard expressions 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, accessing the value would be unsafe (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 guard declaration 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 guard expressions 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 guard expressions 'if var <- expr()' with compiler-enforced scoping.
Keywords: safe, guard, access, E08030, chain, optional, E08040, absent, isset, nested, null-safe, scope, initialize, safety, data-flow, multiple