What is the difference between require, assert, and throw?
← Error Handling and Exceptions · Ref: Q305
EK9 provides three distinct mechanisms for checking conditions, each for a different context.
REQUIRE (Production Preconditions)
Context: Any code (production, libraries, frameworks).
Behavior: When the condition is false or unset, the language raises a Panic, which no catch can see. The program ends with exit status 1 after its finally blocks run.
Purpose: Enforce contracts and preconditions. Caller has a bug if this fails.
Catchable: No. There is no recovery.
Always active: Yes. Cannot be disabled.
Example: require quantity >= 0
ASSERT (Test Validation)
Context: Only valid in dev source files (a dev/ directory or a file named with _dev_). Using assert in any other source file is compile error E81015, and an assert in dev code that a production program can reach is E81012.
Behavior: When the condition is false, the test fails with structured output showing file, line, column, and the failed expression text, and 'ek9 -t' exits 11.
Purpose: Verify expected behavior in tests.
Catchable: Not applicable (test infrastructure handles failures).
Always active: Yes, within test context.
Example: assert result == 42
THROW (Recoverable Exceptions)
Context: Any code.
Behavior: Throws a catchable exception that can be handled with try/catch. If nothing catches it, the program ends with the exception's exit code, or 1 when it has none.
Purpose: Signal exceptional but potentially recoverable conditions.
Catchable: Yes, with try/catch.
Example: throw Exception("File not found")
DECISION GUIDE
Is this a test? Use assert.
Is this a programming error or contract violation? Use require.
Could this reasonably happen at runtime and be recovered from? Use throw.
Is this expected failure in normal flow? Use Result type instead.
KEY DISTINCTIONS
- require is stronger than throw because it cannot be caught.
- assert is compile-time restricted to dev sources, preventing production misuse.
- throw and try/catch are for runtime error recovery.
- Result is for expected failures that are not exceptional.
See Q134 for try/catch. See Q136 for throwing exceptions. See Q139 for try/catch vs Result. See Q304 for require in depth. See Q1431 for how each of these sets a program's exit status.
Example
defines module qa.errorhandling.require.vs.assert.vs.throwex defines class <?- Simple domain class used in examples below. -?> Account balance <- Float() Account() -> initialBalance as Float require initialBalance? require initialBalance >= 0.0 balance :=: initialBalance deposit() -> amount as Float require amount? require amount > 0.0 balance += amount withdraw() -> amount as Float require amount? require amount > 0.0 if amount > balance throw Exception("Insufficient funds") balance -= amount getBalance() as pure <- rtn as Float: balance override operator ? as pure <- rtn as Boolean: balance? operator $ as pure <- rtn as String: `Account[balance=${balance}]` defines program RequireVsAssertVsThrowDemo() stdout <- Stdout() stdout.println("=== require: enforcing preconditions ===") account <- Account(100.0) stdout.println("Created: " + $account) account.deposit(50.0) stdout.println("After deposit: " + $account) stdout.println("=== throw: recoverable exception ===") try account.withdraw(200.0) catch -> ex as Exception stdout.println("Caught: " + ex.reason()) stdout.println("Account still usable: " + $account) account.withdraw(30.0) stdout.println("After valid withdraw: " + $account) stdout.println("=== Result: expected failures ===") stdout.println("Use Result for operations where failure is normal,") stdout.println("like parsing user input or searching a collection.")
Common mistakes
E07530 — The require statement needs a Boolean expression. Passing a Float directly triggers E07530 — only compatible with Boolean type. Use a comparison like 'require amount > 0.0' or 'require amount?' to check if set. See ek9 -h E07530 for details.
Incorrect:
require amount balance += amount
Correct:
require amount > 0.0 balance += amount
E04030 — The throw statement requires a value that extends Exception. Throwing a regular class like Account triggers E04030 — type must be of Exception type. Create a custom exception with 'extends Exception' for domain-specific errors. See ek9 -h E04030 for details.
Incorrect:
throw Account(100.0)
Correct:
throw Exception("Insufficient funds")
Other ways to ask this
- When should I use require vs assert vs throw?
- How do I choose between require, assert, and throw?
- What is the EK9 error handling hierarchy?
Coming from another language?
Java: assert (can be disabled with -da), throw/try/catch, no require equivalent. Python: assert (disabled with -O), raise/try/except, no require. Go: panic (uncatchable without recover), no assert keyword, error return values. Rust: panic! (uncatchable), assert! (active in debug, removed in release with --release), Result for recoverable errors. Kotlin: require() (catchable IllegalArgumentException), assert (JVM assert), throw/try/catch. C/C++: assert() (disabled with NDEBUG), throw/try/catch. EK9: require is always active and uncatchable (stronger than all), assert is compile-time restricted to tests (cannot misuse in production), throw/try/catch for recoverable situations.
Keywords: comparison, E81012, decision, throw, exception, assert, test, catch, handle, precondition, uncatchable, require