Why must I use a guard expression before accessing a returned value?

← Data Flow Safety · Ref: Q634

EK9 requires guard expressions to verify that a value is set before calling methods on it. Without the guard, the value might be unset, and calling a method on an unset value is unsafe.

GUARD EXPRESSION 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). Without a guard, calling methods on an unset value would be like calling methods on null in Java. EK9 prevents this at compile time rather than crashing at runtime.

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(`Found: ${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 guard declaration 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 guard expressions 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 guard expressions, 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: guard expression 'if var <- expr()' enforced by compiler, variable only in scope when set.

Keywords: isSet, unset, method, migrate, null, guard, null-safe, access, optional, expression, absent, E08030, data-flow, E08040, initialize, safe, isset, safety