How does EK9's Result type differ from other languages?

← Advanced Type System · Ref: Q259

EK9's Result type has four states instead of the two states found in most languages. This is a deliberate type system design that handles real-world scenarios other languages cannot express.

FOUR STATES NOT TWO

Most languages (Rust, Swift, Kotlin) treat Result as strictly either success OR error. EK9's Result of (O, E) has four independent states:

  1. Neither: both ok and error are unset. Represents incomplete or pending.
  2. Ok only: success value present, no error.
  3. Error only: error value present, no success.
  4. Both ok AND error: success AND error both present simultaneously.

WHY FOUR STATES?

Real-world operations genuinely produce both success and error simultaneously:

  Configuration lookup fails but returns a default value plus an error code.
  Data migration converts a record but logs warnings.
  API returns partial results with error details about missing fields.

In these cases, the caller needs BOTH the usable result AND the error information. Rust's Result forces you to choose one or the other.

INDEPENDENT CHECKS

isOk() and isError() are independent boolean checks. Both can return true:

  r <- Result("Default", -1)
  r.isOk()    // true
  r.isError() // true

The ? operator checks isOk (biased toward success).

COMPILE-TIME GUARD ENFORCEMENT

The compiler enforces that you cannot access .ok() without an isOk() guard, and cannot access .error() without an isError() guard. There is no .unwrap() that panics at runtime. No escape hatches.

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

CONTRAST WITH OTHER LANGUAGES

  Rust: Result<T,E> is strictly Ok OR Err. .unwrap() panics at runtime. Forces choice between success and error.
  Go: returns (value, error) tuple. No compiler enforcement. Caller can ignore error. No type safety.
  Java: no Result type. Uses exceptions which are invisible in method signatures.
  Swift: Result<Success,Failure> is strictly success OR failure, like Rust.
  Kotlin: kotlin.Result exists but no compiler-enforced safe access.

EK9's four-state model with compile-time guards is unique. It eliminates the entire class of unwrap panics (Rust), ignored errors (Go), and unchecked exceptions (Java).

See Q48 for Result basics and creation. See Q86 for advanced guard patterns. See Q87 for Result operations. See Q139 for choosing between try/catch and Result.

Example

defines module qa.advancedtypes.resulttypesystem

  defines function

    <?-
      Returns ok only: configuration found.
    -?>
    getConfig()
      <- rtn <- Result("production", Integer())

    <?-
      Returns error only: lookup failed.
    -?>
    getFailedConfig()
      <- rtn <- Result(String(), 404)

    <?-
      Returns BOTH ok and error: fallback used with warning.
    -?>
    getConfigWithFallback()
      <- rtn <- Result("default-value", -1)

    <?-
      Returns neither: operation not yet complete.
    -?>
    getPendingConfig()
      <- rtn <- Result() of (String, Integer)

  defines program

    ResultTypeSystemDemo()
      stdout <- Stdout()

      // === FOUR STATES ===

      okResult <- getConfig()
      errorResult <- getFailedConfig()
      bothResult <- getConfigWithFallback()
      neitherResult <- getPendingConfig()

      // isOk and isError are independent
      stdout.println(`Ok result - isOk: ${okResult.isOk()}, isError: ${okResult.isError()}`)
      stdout.println(`Error result - isOk: ${errorResult.isOk()}, isError: ${errorResult.isError()}`)
      stdout.println(`Both result - isOk: ${bothResult.isOk()}, isError: ${bothResult.isError()}`)
      stdout.println(`Neither result - empty: ${neitherResult is empty}`)

      // === COMPILE-TIME GUARDS ===

      // Guard required before accessing .ok()
      if okResult.isOk()
        stdout.println(`Config: ${okResult.ok()}`)

      // Guard required before accessing .error()
      if errorResult.isError()
        stdout.println(`Error code: ${errorResult.error()}`)

      // AND guard: both checks satisfied, both accesses safe
      if bothResult.isOk() and bothResult.isError()
        stdout.println(`Fallback value: ${bothResult.ok()}`)
        stdout.println(`Warning code: ${bothResult.error()}`)

      // === SAFE DEFAULTS ===

      // No guard needed with defaults
      safeConfig <- errorResult.okOrDefault("fallback")
      stdout.println(`Safe config: ${safeConfig}`)

      safeError <- okResult.errorOrDefault(0)
      stdout.println(`Safe error: ${safeError}`)

Common mistakes

E08030 — Guarding with isError() does not prove isOk() is true. Accessing .ok() requires an isOk() guard. The compiler tracks which guard was used and rejects mismatched access, triggering E08030. See ek9 -h E08030 for details.

Incorrect:

if okResult.isError()

Correct:

if okResult.isOk()

E08030 — Changing 'and' to 'or' means neither isOk() nor isError() is individually guaranteed. With 'and', both guards are satisfied so both .ok() and .error() are safe. With 'or', either could be false, so the compiler rejects access to both, triggering E08030. See ek9 -h E08030 for details.

Incorrect:

isOk() or bothResult.isError()

Correct:

isOk() and bothResult.isError()
Other ways to ask this
  • Why does EK9's Result have four states?
  • How does Result's four-state model work?
  • What makes EK9's Result unique compared to Rust's Result?

Coming from another language?

Rust: Result<T,E> is strictly Ok or Err (two states), .unwrap() panics at runtime, ? operator for propagation, forces Either semantics. Go: (value, error) return convention, no compiler enforcement, errors easily ignored, nil error means success. Java: no Result type, checked exceptions in signatures but unchecked exceptions invisible, try-catch is verbose. Swift: Result<Success,Failure> with associated values, strictly two states, get() throws on failure. Kotlin: kotlin.Result wrapping, no compiler-enforced guards, .getOrThrow() can fail. Haskell: Either monad is strictly Left or Right. EK9: Result of (O, E) has FOUR states (neither, ok-only, error-only, BOTH), compile-time guard enforcement, no unwrap, no escape hatches.

Keywords: both, migrate, enforce, isset, four, safety, unwrap, unique, type-system, safe, independent, differ, state, ok, null-safe, guard, type, error, result, advanced, panic, compile