Skip to content

Volume 13: Error Handling

1. Introduction

This volume defines error handling in the Rethon programming language. It describes the error handling model, recoverable errors, unrecoverable errors, result types, option types, throwing functions, try statements, except clauses, finally clauses, raise statements, error types, error protocols, pattern matching errors, error propagation, resource cleanup, diagnostics and stack information, future extensions, and summary.

This document is part of the official Rethon Language Specification. It is normative rather than descriptive. Implementations, tooling, documentation systems, and other source-processing tools shall treat the rules in this volume as authoritative.

This volume does not define backend exception machinery, unwinding algorithms, ABI layout, or runtime-specific stack representation. It defines the source-level and language-level behavior of error handling as programmer-visible control flow.

Rethon is Python-inspired in language philosophy and Python-first in surface syntax where practical. It uses static typing, explicit result modeling where appropriate, enums with associated values, pattern matching, protocols as behavior contracts, memory safety by default, and readable control flow.

Rethon shall support both explicit result modeling and exception-style error handling at the source level.

2. Error Handling Model

Rethon shall support two primary styles of error handling:

  • explicit result modeling for ordinary recoverable failures
  • try/except/finally/raise-based error handling for exceptional control flow, boundary handling, and interoperability

The language shall favor explicit, statically typed failure modeling where that form improves readability, predictability, performance, or API stability.

The language shall also support exception-style control flow where that form is clearer or more practical for exceptional conditions.

Error handling shall remain source-readable and memory-safe by default.

Example:

def parse_int(text: str) -> Result<int, ParseError>:
    ...

try:
    value = parse_int(text)
except ParseError as error:
    print(error.message)

Recoverable failures shall ordinarily be modeled explicitly.

Unrecoverable failures shall be separated from ordinary recoverable control flow.

3. Recoverable Errors

Recoverable errors shall be errors that a caller is reasonably expected to handle as part of ordinary program logic.

Examples of recoverable errors include:

  • input validation failures
  • missing data
  • parse failures
  • lookup failures
  • permission results that are expected to be checked explicitly
  • API outcomes that are part of ordinary domain flow

Recoverable errors should normally be modeled with explicit result types.

Recoverable errors should not be forced into exception-style control flow when a typed result would better express the ordinary API contract.

Recoverable errors shall remain statically typed where practical.

Example:

def lookup_user(id: int) -> Result<User, LookupError>:
    ...

When a recoverable failure is part of a stable API surface, a typed result shall be preferred.

4. Unrecoverable Errors

Unrecoverable errors shall be errors that indicate a failure condition not intended to be handled as ordinary program flow.

Examples of unrecoverable errors include:

  • invariant violations
  • impossible states reached by implementation defects
  • serious runtime failures that prevent ordinary continuation
  • boundary failures that must propagate until a suitable handler is found

Unrecoverable errors shall be separated from recoverable errors in source-level intent.

A program may use raise for unrecoverable conditions when the language form is appropriate.

Unrecoverable errors shall not be treated as a substitute for ordinary typed result modeling.

5. Result Types

Result types shall be supported as the preferred explicit model for ordinary recoverable errors.

A result type shall represent either a success value or an error value.

Result types shall be suitable for APIs where explicit success and failure handling improves clarity, performance, stability, or static reasoning.

Example:

enum Result<T, E>:
    success(value: T)
    failure(error: E)

A result type shall be statically typed.

A result type shall be compatible with pattern matching.

A result type shall be appropriate for parse operations, validation, database access, file operations, and other domain APIs that benefit from explicit success/failure values.

Pattern matching shall be the preferred way to inspect result-style errors.

Example:

match parse_result:
    case Result.success(value: value):
        print(value)
    case Result.failure(error: error):
        print(error.message)

A result type shall not be required to be implemented as a built-in runtime mechanism. The specification defines the language-level model and the semantics of such types.

6. Option Types

Option types shall be supported as a Standard Library abstraction for explicit optional-presence modeling when values may be present or absent as part of the ordinary API contract.

The authoritative language-level definition of nullable types and explicit absence is defined in the type system volume. Option<T> complements nullable types rather than replacing them.

An option type shall represent either a present value or the absence of a value.

Example:

enum Option<T>:
    some(value: T)
    none

The absent variant of Option<T> is part of the Option<T> abstraction. It does not introduce a second built-in absence literal distinct from None.

Option types shall be statically typed.

Option types shall be useful when absence is a meaningful and ordinary outcome rather than an exceptional condition.

Option types shall remain compatible with pattern matching.

Example:

match nickname:
    case Option.some(value: value):
        print(value)
    case Option.none:
        print("anonymous")

Option types shall be preferred over exception-style handling when the absence of a value is part of the ordinary API contract.

7. Throwing Functions

Throwing functions shall be supported.

A throwing function shall be a function that can signal an error through raise and that requires callers to handle or propagate the thrown error according to the language's source-level rules.

Example:

def load_config(path: str) -> Config throws ConfigError:
    ...

The exact surface notation for declaring a throwing function shall remain consistent with the language's Python-first surface direction while still making throwing behavior explicit.

Throwing functions shall be statically typed where practical.

A throwing function shall not be treated as ordinary result-returning code unless the source declares that behavior explicitly.

Throwing functions shall remain suitable for boundary APIs, interoperability layers, and exceptional control flow.

8. try Statements

try statements shall be supported.

A try statement shall evaluate a protected block and route thrown errors to matching handlers when a handler applies.

Example:

try:
    value = load_config(path)
except ConfigError as error:
    print(error.message)

A try statement shall use Python-like surface syntax.

A try statement shall allow one or more handler clauses and an optional finally clause.

A try statement shall be allowed wherever a statement is permitted by the language's block and scope rules.

The try statement shall remain compatible with readable indentation-based source structure.

9. except Clauses

except clauses shall be supported.

An except clause shall match a thrown error by type, by protocol-constrained type, or by another statically defined error pattern the language permits.

Example:

try:
    value = load_config(path)
except ConfigError as error:
    print(error.message)
except IOError:
    print("I/O failure")

An except clause may bind the thrown error to a name.

An except clause shall be evaluated in source order.

The first matching except clause shall be selected.

An except clause shall not conceal errors unless the source intentionally handles or re-raises them.

except clauses shall remain compatible with typed error handling and with source-level readability.

10. finally Clauses

finally clauses shall be supported.

A finally clause shall run after the protected block completes, whether completion is normal, handled by an except clause, or interrupted by a re-raised error.

Example:

try:
    file = open_file(path)
    ...
finally:
    file.close()

A finally clause shall be suitable for cleanup, release, and finalization logic.

A finally clause shall be the initial resource-cleanup mechanism of the language.

A with form or context-manager mechanism may be introduced later if a future specification decides that it improves ergonomics without weakening readability or safety.

11. raise Statements

raise statements shall be supported.

A raise statement shall signal an error value or error type according to the language's typing rules.

Example:

def parse_age(text: str) -> int throws ParseError:
    if text == "":
        raise ParseError(message: "empty input")
    return parse_int(text)

A raise statement shall be used to signal exceptional failure conditions.

A raise statement shall not be the preferred model for ordinary recoverable outcomes when a typed result is a clearer source-level contract.

A raise statement shall be compatible with static typing, error protocols, and the language's memory-safety goals.

12. Error Types

Error types shall be supported.

An error type shall be a statically typed value form used to represent failure information.

Example:

struct ParseError: Error:
    message: str

An error type may be a struct, class, or enum, subject to the language's type rules.

Error types shall be useful for diagnostics, pattern matching, recovery, and propagation.

Error types shall remain explicit and source-visible.

Thrown errors shall be typed where practical.

When a program raises an error, the error value shall have a statically known type or a source-level type that the compiler can validate according to the language's typing rules.

13. Error Protocols

Error protocols shall be supported.

An error protocol shall describe behavior common to error values.

Example:

protocol Error:
    message: str

Error protocols shall allow common handling, formatting, and diagnostics behavior to be expressed as a contract rather than as a storage model.

Error types should conform to error protocols when that improves composition, reuse, and static checking.

Error protocols shall be compatible with the protocol model established elsewhere in the specification.

An error protocol shall not require a particular runtime exception representation.

14. Pattern Matching Errors

Pattern matching shall be the preferred way to inspect result-style errors and option-style values.

Example:

match result:
    case Result.success(value: value):
        print(value)
    case Result.failure(error: error):
        print(error.message)

Pattern matching shall be especially appropriate for closed error sets modeled as enums with associated values.

Pattern matching shall remain statically typed and shall respect the language's exhaustiveness goals where applicable.

Pattern matching on error values shall not depend on hidden runtime details.

A result or option value should be inspected with pattern matching when that form preserves the language's readability and explicitness goals better than nested conditionals.

15. Error Propagation

Error propagation shall be explicit.

A thrown error shall propagate until it is handled by a matching except clause, converted into a result value, or allowed to terminate the current control flow according to the language's rules.

A result value shall propagate through ordinary value flow until the source explicitly inspects it.

A function may translate between thrown errors and result values when that translation improves the API boundary or the source's clarity.

Example:

def read_user(id: int) -> Result<User, Error>:
    try:
        return Result.success(value: load_user(id))
    except UserError as error:
        return Result.failure(error: error)

Error propagation shall preserve typing, readability, and explicit control flow.

16. Resource Cleanup

Resource cleanup shall initially use finally.

A finally clause shall be the primary guaranteed-cleanup form in the language until a later volume introduces a more specialized construct such as with or context-manager handling.

Cleanup logic shall remain explicit and source-readable.

Example:

try:
    connection = open_connection(url)
    send_request(connection)
finally:
    connection.close()

The language shall not require hidden cleanup behavior to make ordinary code correct.

A future with form may be considered if it improves ergonomics, but such a form shall not replace the current requirement that cleanup remain explicit and safe.

17. Diagnostics and Stack Information

Error diagnostics shall support useful source-level information.

When the compiler or runtime reports an error, the information should include the location or locations needed to understand the failure clearly.

If stack information is available, it should be surfaced in a source-readable way that helps developers trace failures without exposing backend representation details.

Diagnostics shall prioritize clarity over implementation-specific detail.

Error objects may carry diagnostic fields or context values if the language design or surrounding library conventions choose to expose them.

The specification does not require any specific stack-trace format.

18. Future Extensions

This volume defines the current direction for error handling in Rethon, but future specification work may refine the model in carefully controlled ways.

Possible future extensions include:

  • a dedicated unit/no-value type distinct from None
  • additional sugar for propagating result values
  • with or context-manager forms for structured cleanup
  • richer diagnostic metadata on error values
  • specialized recovery helpers for common APIs
  • further integration between pattern matching and error propagation
  • more explicit syntax for throwing-function declarations if later needed

Any future extension shall preserve the current direction:

  • Python-like try, except, finally, and raise remain available
  • explicit Result<T, E> style modeling remains encouraged for ordinary recoverable failures
  • Option<T> remains the explicit absence model
  • recoverable errors prefer typed results in stable or performance-sensitive APIs
  • unrecoverable errors remain distinct from recoverable ones
  • thrown errors remain typed where practical
  • error types may conform to protocols
  • pattern matching remains the preferred inspection style for result-like values
  • resource cleanup begins with finally

19. Summary

Rethon shall support both explicit result modeling and exception-style error handling.

Ordinary recoverable failures should prefer typed result values such as Result<T, E> or Option<T>.

Python-like try, except, finally, and raise shall be supported for exceptional control flow, boundary handling, and interoperability.

Thrown errors shall be typed where practical.

Error types may conform to protocols.

Pattern matching shall be the preferred way to inspect result-style errors.

Resource cleanup shall initially use finally, with with deferred to future consideration.

Error handling in Rethon shall therefore remain explicit, statically typed, memory-safe, and readable while still supporting practical exceptional control flow.