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