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, throws an uncatchable exception. Program terminates.
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 inside @Test programs. Using assert in production code causes compile error E81012.
Behavior: When the condition is false, the test fails with structured output showing file, line, column, and the failed expression text.
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.
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 tests, 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.
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
Correct:
require amount > 0.0
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: catch, test, assert, exception, throw, decision, E81012, comparison, require, uncatchable, precondition, handle