How should Result values be accessed safely using guard expressions?

← Data Flow Safety · Ref: Q687

Result values must be accessed through guard expressions that ensure the success or error case is properly checked before use.

GUARD ACCESS PATTERN (E08030)

The correct way to access a Result is through a guard:

  if result.isOk()
    okValue <- result.ok()
    process(okValue)

Accessing the result value outside a guard risks using an error value as if it were a success, triggering E08030.

SAFE DEFAULT PATTERN

The simplest approach uses okOrDefault:

  safeValue <- result.okOrDefault(fallback)

This always returns a usable value without risk.

NO REASSIGNMENT IN GUARD (E08040)

Once a guard variable is bound, it cannot be reassigned within the guard block. The variable is a one-time extraction.

See Q634 for guard expressions. See Q636 for chained guards. See Q306 for Result type basics.

Example

defines module qa.dataflow.resultguard

  defines constant

    DEFAULT_AGE <- 0
    UNKNOWN_LABEL <- "unknown"

  defines function

    <?-
      Function returning a Result.
      Demonstrates producing success and error cases.
    -?>
    parseAge() as pure
      -> ageText as String
      <- parsed as Result of (Integer, String): Result(Integer(), "no input")

      if ageText?
        parsed: Result(Integer(ageText), String())

    <?-
      Correct: use okOrDefault for simple safe access.
      No guard needed when a default is acceptable.
    -?>
    getAgeOrDefault() as pure
      -> ageText as String
      <- ageValue as Integer: DEFAULT_AGE

      result <- parseAge(ageText)
      ageValue: result.okOrDefault(DEFAULT_AGE)

    <?-
      Correct: use isOk() guard before accessing ok().
      Check and access in the same syntactic scope.
    -?>
    describeAge()
      -> ageText as String
      <- description as String: UNKNOWN_LABEL

      result <- parseAge(ageText)

      if result.isOk()
        actualAge <- result.ok()
        description: `Age: ${actualAge}`

  defines program

    ResultGuardAccessDemo()
      stdout <- Stdout()

      stdout.println(`Default: ${getAgeOrDefault("25")}`)
      stdout.println(`No input: ${getAgeOrDefault("")}`)

      stdout.println(describeAge("30"))
      stdout.println(describeAge(""))

Common mistakes

E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.

Incorrect:

stdout.println(describeAge("30").toUpperCase())

Correct:

stdout.println(describeAge("30"))

E50060 — Integer has no intValue() method in EK9. Integer values are used directly. See ek9 -h E50060 for details.

Incorrect:

ageValue: result.okOrDefault(DEFAULT_AGE).intValue()

Correct:

ageValue: result.okOrDefault(DEFAULT_AGE)
Other ways to ask this
  • What is E08030 result value accessed outside guard?
  • What is E08040 result guard reassignment?
  • How do I safely unwrap a Result in EK9?
  • What is the correct pattern for Result guard access?

Coming from another language?

Java: Optional.ifPresent() or manual null checks. Rust: match on Result with Ok/Err. Go: explicit error return values (val, err). Python: try/except. EK9: guard expressions with isOk()/isError() or okOrDefault() with compile-time enforcement.

Keywords: access, E08040, E08030, unwrap, safety, initialize, null-safe, ok, guard, data-flow, error, safe, result, isset