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