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