How does the Result type work in EK9?

← Getting Started · Ref: Q48

Result of (O, E) is a generic type for operations producing a success value, error value, or BOTH. Unlike Rust's either/or Result, EK9's can hold both simultaneously.

FOUR POSSIBLE STATES

1. Neither ok nor error: Result() of (String, Integer)
2. Ok only: Result("Steve", Integer())
3. Error only: Result(String(), -1)
4. Both ok AND error: Result("Default", -1)
The fourth state handles cases like a failed config lookup that still returns a fallback with an error code.

RESULT CREATION

  r1 <- Result("Steve", Integer())            two-arg, ok only
  r2 <- Result() of (String, Integer)          empty
  r3 <- (Result() of (String, Integer)).asOk("Steve")   factory
  r4 <- (Result() of (String, Integer)).asError(-1)      factory

CHECKING STATE

  if r?                checks isOk
  if r.isOk()          explicit ok check
  if r.isError()       explicit error check
  r is empty           neither ok nor error

isOk() and isError() are INDEPENDENT — both can be true.

BASIC GUARD PATTERNS

Compiler enforces guard-before-access:

  if r <- getResult()
    okData <- r.ok()
  if r.isError()
    errData <- r.error()

OK OR DEFAULT / ERROR OR DEFAULT

  assured <- r.okOrDefault("fallback")
  errVal <- r.errorOrDefault(0)

See Q86 for advanced guard patterns. See Q87 for Result operations (whenOk/whenError, merge, copy, comparison, iterator, contains). See Q47 for Optional. See Q29 for tri-state semantics. See Q54 for Consumer/Acceptor. See Q126 for choosing collection types.

Use 'ek9 -h Result' for the full API.

See Q139 for try/catch vs Result. See Q243 for coalescing operators. See Q251 for unset variable errors. See Q259 for four-state Result design. See Q316 for discarded Result errors.

Example

defines module qa.result

  defines function

    <?-
      Returns a Result with ok value set.
    -?>
    getOkResult()
      <- rtn <- Result("Steve", Integer())

    <?-
      Returns a Result with error value set.
    -?>
    getErrorResult()
      <- rtn <- Result(String(), -1)

    <?-
      Returns a Result with both ok and error set.
    -?>
    getBothResult()
      <- rtn <- Result("Default", -1)

    <?-
      Returns an empty Result.
    -?>
    getEmptyResult()
      <- rtn <- Result() of (String, Integer)

  defines program
    ResultBasics()
      stdout <- Stdout()

      // === FOUR STATES ===

      okResult <- Result("Steve", Integer())
      errorResult <- Result(String(), -1)
      bothResult <- Result("Default", -1)
      emptyResult <- Result() of (String, Integer)

      stdout.println(`Ok result: ${okResult}`)
      stdout.println(`Error result: ${errorResult}`)
      stdout.println(`Both result: ${bothResult}`)
      stdout.println(`Empty result: ${emptyResult}`)

      // === CHECKING STATE ===

      // ? checks isOk, isOk/isError are independent
      stdout.println(`okResult?: ${okResult?}, isOk: ${okResult.isOk()}, isError: ${okResult.isError()}`)
      stdout.println(`errorResult?: ${errorResult?}, isOk: ${errorResult.isOk()}, isError: ${errorResult.isError()}`)
      stdout.println(`bothResult?: ${bothResult?}, isOk: ${bothResult.isOk()}, isError: ${bothResult.isError()}`)
      stdout.println(`emptyResult is empty: ${emptyResult is empty}`)

      // === CREATION WITH FACTORY METHODS ===

      factoryOk <- (Result() of (String, Integer)).asOk("Alice")
      factoryErr <- (Result() of (String, Integer)).asError(99)
      stdout.println(`Factory ok: ${factoryOk}`)
      stdout.println(`Factory error: ${factoryErr}`)

      // === BASIC GUARD PATTERNS ===

      // Declaration guard — ? check implied
      if r <- getOkResult()
        okData <- r.ok()
        stdout.println(`Declaration guard: ${okData}`)

      // Explicit isOk check
      r1 <- getOkResult()
      if r1.isOk()
        stdout.println(`isOk guard: ${r1.ok()}`)

      // Explicit isError check
      r2 <- getErrorResult()
      if r2.isError()
        stdout.println(`isError guard: ${r2.error()}`)

      // Dual guard — both ok and error
      r3 <- getBothResult()
      if r3.isOk()
        stdout.println(`Both ok side: ${r3.ok()}`)
      if r3.isError()
        stdout.println(`Both error side: ${r3.error()}`)

      // Empty result — neither guard enters
      if r4 <- getEmptyResult()
        stdout.println("Should not print")

      // === OK OR DEFAULT / ERROR OR DEFAULT ===

      r5 <- getOkResult()
      assured <- r5.okOrDefault("Fallback")
      stdout.println(`okOrDefault (set): ${assured}`)

      r6 <- getEmptyResult()
      defaulted <- r6.okOrDefault("Fallback")
      stdout.println(`okOrDefault (empty): ${defaulted}`)

      r7 <- getErrorResult()
      errVal <- r7.errorOrDefault(0)
      stdout.println(`errorOrDefault (set): ${errVal}`)

      r8 <- getEmptyResult()
      errDefault <- r8.errorOrDefault(0)
      stdout.println(`errorOrDefault (empty): ${errDefault}`)

Common mistakes

E08030 — Calling .ok() on a Result without first checking isOk() triggers E08030 — has not been checked before access. The compiler enforces guard-before-access for both .ok() and .error(). See ek9 -h E08030 for details.

Incorrect:

stdout.println(`isOk guard: ${r1.ok()}`)

Correct:

if r1.isOk()
        stdout.println(`isOk guard: ${r1.ok()}`)

E06190 — Result requires two different types for ok and error values. Using the same type for both triggers E06190 — Result must be used with two different types. This ensures the compiler can distinguish ok from error. See ek9 -h E06190 for details.

Incorrect:

okResult <- Result("Steve", String())

Correct:

okResult <- Result("Steve", Integer())

E11052 — Calling a function that returns Result but discarding the result triggers E11052 — Result or Optional result is discarded and must always be checked. Always assign and check Results. See ek9 -h E11052 for details.

Incorrect:

getOkResult()
      assured <- getOkResult().okOrDefault("Fallback")

Correct:

r5 <- getOkResult()
      assured <- r5.okOrDefault("Fallback")
Other ways to ask this
  • How do I handle success and error values in EK9?
  • What is the difference between Result and Optional in EK9?
  • How do I safely access ok and error values from a Result?
  • How do I handle errors without exceptions in EK9?
  • What do I use instead of try-catch in EK9?
  • How do I handle a function that might fail in EK9?

Coming from another language?

Java: no Result, try-catch. Rust: Result<T,E> either/or only, .unwrap() panics. Go: (value, error) tuple, no enforcement. Kotlin/Swift: Result exists but no compiler-enforced access. EK9: four states (both ok AND error), compiler-enforced guards, okOrDefault/errorOrDefault.

Keywords: fail, first, failure, catch, isOk, safe, intro, swift, isError, guard, ok, errorOrDefault, beginner, error, okOrDefault, null-safe, try, start, compiler, result, handle, type, isset, success, migrate, exception