Exceptions and contracts¶
Operational failures, invalid external input, and broken programmer obligations need different treatment. Declared exceptions and checked contracts make those distinctions visible.
Design documentation
This chapter describes the language design. Examples are illustrative; implementation availability and unresolved rules are explained in Design status.
Choosing a failure representation¶
| Situation | Representation |
|---|---|
| An expected absence or end of iteration | Ordinary control flow or a result value |
| An operation fails in a way its callers may need to handle | A declared ordinary exception |
| External input fails parsing or domain validation | An ordinary result or declared exception |
| A caller breaks a precondition, or an implementation breaks a postcondition | ContractViolation |
| A test expectation is false | A test-only assertion |
For example, an invalid uploaded date is external-input validation. It should not become a contract violation merely because the parser cannot accept it. A precondition on an internal API can instead express a caller obligation.
Arbor prefers exceptions over return codes for exceptional failures, with explicit
contracts for ordinary escaping exceptions. Expected conditions have ordinary
control-flow representations. Iteration does not use a fake StopIteration error.
Fundamental categories¶
| Type | Contract |
|---|---|
BaseException |
Abstract root; cannot be thrown or caught directly |
Exception |
Cannot be thrown; catch-only umbrella for ordinary exceptions |
NotImplementedError |
Undeclared development/test failure; unfinished implementations are rejected in production |
RuntimeError |
Unexpected runtime/platform invariant failure, potentially including memory corruption |
KeyboardInterrupt |
Generated for Ctrl-C; cannot be explicitly thrown |
SystemExit |
Immediate exit, default status 1 |
ContractViolation |
Undeclared programmer-contract failure, outside the ordinary catch-all |
Runtime errors, keyboard interruption, and system exit are outside Exception.
Catch-all application handling must not accidentally swallow them. Contract
violations can be caught explicitly, including by tests and application boundaries.
Detailed handling rules for other fundamental categories remain open.
Thrown exceptions must be instances:
throw InvalidArgument("Expected a positive value")
throw SystemExit()
throw SystemExit(status=0)
throw SystemExit // Error: this is a type, not an instance.
Exception selectors in handlers are types; the instantiation rule applies to throwing, not to selecting a handler.
Production builds reject standalone unfinished-body ellipses and explicit throws
of NotImplementedError in included production code, even behind a currently
false condition. Tests are excluded from that production-code check. Exact
dependency/build-artifact enforcement remains to be specified.
Actual memory corruption may make exception construction or cleanup unsafe and
require a fail-fast runtime path. Immediate SystemExit has no normal cleanup
guarantee. Ctrl-C cancellation and cleanup policy remain open.
Declared operational exceptions¶
Ordinary exceptions escaping a function belong in its declared contract. The compiler checks catching and propagation. Internal effect inference can assist implementation, while public APIs remain explicit. Precise syntax, hierarchy coverage, generic effects, and the implicit built-in failure set remain open.
Asking whether an operation is valid is preferred to using exceptions for routine branching. For concurrent or OS operations, a preliminary check is not atomic; APIs still need atomic operations and meaningful operational errors. The compiler may prove or move redundant checks rather than mandating repeated checks in loops.
Validation without signature pollution¶
validate(x, positive)
pure fun square(x: int) -> int
ensures(result >= 0)
throws ArithmeticOverflow
{
return x * x
}
Syntax is provisional. Contracts express API obligations and promised results. Squaring promises nonnegative, not strictly positive, because zero is valid.
- Preconditions constrain arguments; postconditions constrain successful results.
- The compiler proves contracts when possible and otherwise checks them at runtime.
- Unproved checks remain enabled in production.
- Failure raises
ContractViolationwithout adding it to propagatedthrowslists. - Callers need not add wrappers or statically prove every obligation.
- A caller precondition failure is distinguished from an implementation failing its postcondition.
- Predicates are pure; machine-readable contract expressions are retained in compiled metadata where possible.
- Redundant checks can be removed only when proven unnecessary. Opaque predicates may still have runtime costs.
Malformed external input is not automatically a programming mistake: a parser or domain-validation API reports expected invalid input as an ordinary result or declared exception. It cannot evade its operational contract by relabeling every bad input a contract violation.
Assertions are test-only. Production validation uses contracts or validate.