What is the require statement and when should I use it?

← Error Handling and Exceptions · Ref: Q304

The require statement validates preconditions in production code. When a require condition is false or unset, the language raises a Panic: it is not an Exception, so no catch can see it, and the program ends with exit status 1 once its finally blocks and teardown have run. This is similar to panic in Go or Rust.

KEY CHARACTERISTICS

- Uncatchable: require failures cannot be caught with try/catch. There is no recovery.
- Always active: unlike Java assert which can be disabled, EK9 require is always checked.
- For programming errors: use require when a failure means the caller has a bug.
- Fail-fast: detects contract violations immediately rather than allowing corrupt state.

WHEN TO USE REQUIRE

Use require for:
- Null/unset parameters when the method cannot accept unset values.
- Collection must not be empty (require not items.empty()).
- Method called in wrong object state (require status == OrderStatus.Created).
- Internal invariants that should never be violated.

WHEN NOT TO USE REQUIRE

Do not use require for:
- User input validation (use catchable exceptions or Result instead).
- File not found or network timeout (these are recoverable).
- Any condition that could reasonably fail due to external factors.

REQUIRE VS THROW

require: For programming errors and contract violations. Uncatchable. Similar to panic.
throw: For exceptional but potentially recoverable situations. Catchable with try/catch.

REQUIRE AND RETURN ORDERING (E08055)

When a construct has both a require and a returning variable, declare the return UNINITIALISED (<- rtn as Type?) and assign it only AFTER the require(s). An inline-initialised return (<- rtn as Type: expr) runs BEFORE any require, so a require written to guard the return computation would be dead code - the compiler rejects that shape with E08055. Guard first, then compute. See the calculateTotal example below: the return is declared 'as Float?', the requires come first, then 'rtn: unitPrice * Float(quantity)'. The mandated 'as Type?' form also composes with E08060 - a precondition that references the return would read an uninitialised variable.

See Q134 for try/catch. See Q139 for error handling strategy. See Q305 for how require differs from assert. See Q136 for throwing exceptions. See Q1434 for what a Panic is. See Q1435 for the exit status of a failed require.

Example

defines module qa.errorhandling.preconditions

  defines type

    OrderStatus
      Created
      Submitted
      Shipped

  defines class

    <?-
      Demonstrates require for precondition checking.
      Require validates that callers honour the contract.
    -?>
    Order
      items as List of String: List() of String
      status as OrderStatus: OrderStatus.Created

      <?-
        Constructor with preconditions.
        The caller must provide valid, non-empty data.
      -?>
      Order()
        ->
          customerId as String
          initialItems as List of String
        require customerId?
        require initialItems?
        require not initialItems.empty()
        this.items :=: initialItems

      <?-
        Can only add items while order is in Created state.
        Calling this after submit is a programming error.
      -?>
      addItem()
        -> item as String
        require item?
        require status == OrderStatus.Created
        items += item

      <?-
        Submit the order. Must have items and be in Created state.
      -?>
      submit()
        require not items.empty()
        require status == OrderStatus.Created
        status: OrderStatus.Submitted

      <?-
        Ship the order. Must be in Submitted state.
      -?>
      ship()
        require status == OrderStatus.Submitted
        status: OrderStatus.Shipped

      itemCount() as pure
        <- rtn as Integer: length items

      override operator ? as pure
        <- rtn as Boolean: items? and status?

      operator $ as pure
        <- rtn as String: `Order[${status}, ${itemCount()} items]`

  defines class

    <?-
      Demonstrates require for validating arguments
      in a utility class.
    -?>
    PriceCalculator

      <?-
        Calculate total price. Both arguments must be set and valid.
      -?>
      calculateTotal()
        ->
          unitPrice as Float
          quantity as Integer
        <-
          rtn as Float?

        require unitPrice?
        require quantity?
        require unitPrice >= 0.0
        require quantity >= 0

        rtn: unitPrice * Float(quantity)

  defines program

    RequirePreconditionsDemo()
      stdout <- Stdout()

      stdout.println("=== Order lifecycle with require ===")

      initialItems <- List() of String
      initialItems += "Widget"
      initialItems += "Gadget"

      order <- Order("CUST-001", initialItems)
      stdout.println("Created: " + $order)

      order.addItem("Sprocket")
      stdout.println("Added item: " + $order)

      order.submit()
      stdout.println("Submitted: " + $order)

      order.ship()
      stdout.println("Shipped: " + $order)

      stdout.println("=== Price calculation with require ===")

      calc <- PriceCalculator()
      total <- calc.calculateTotal(9.99, 3)
      stdout.println("Total: " + $total)

Common mistakes

E50001 — Passing 'initialItemList' when only 'initialItems' is declared leaves the name unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details.

Incorrect:

order <- Order("CUST-001", initialItemList)

Correct:

order <- Order("CUST-001", initialItems)
Other ways to ask this
  • How do I enforce preconditions in EK9?
  • What is the EK9 equivalent of assert or panic?
  • How do I validate method arguments in EK9?

Coming from another language?

Go: panic() is uncatchable unless recover() is used. Rust: panic!() unwinds the stack and terminates the thread. Java: assert can be disabled at runtime with -da flag (weak). C/C++: assert() can be compiled out with NDEBUG (weak). Python: assert can be disabled with -O flag. Kotlin: require() throws IllegalArgumentException (catchable, unlike EK9). EK9: require is always active, always uncatchable, stronger than all of these.

Keywords: invariant, guard, require, handle, migrate, safe, exception, contract, null-safe, validate, panic, isset, precondition, catch, uncatchable, argument