Skip to content

Errors and Cleanup

Chloë Strainge edited this page Oct 14, 2026 · 1 revision

Errors and cleanup

Errors can carry typed data. Mark a struct or enum with error, then either return that value through an error-inclusive return type or throw it. ErrorValue is the general constraint for marked error types.

error struct MissingShelf { pub shelf: String }
fn locate($name: String): String | MissingShelf
{
    if $name == "attic" { return MissingShelf(shelf: $name) }
    "ground floor"
}
try
{
    let $location: String = locate "attic"
    echo $location
}
catch ($problem: MissingShelf)
{
    echo "No shelf named" $problem.shelf
}
No shelf named attic

Propagation and handling

A returned marked error normally propagates when it enters an ordinary result context. An explicit scalar type that includes the error can capture it for inspection, but the error remains an obligation to handle or return. Merely storing it in any or an array is not an acknowledgment.

A matching catch acknowledges the error. throw transfers it again. A function whose return type admits the error can return it to its caller. Error unions can provide shared methods while retaining the concrete error's identity.

catch ($stack, $error) receives both context and value. Typed catches select an error type; a final untyped catch handles anything remaining. RuntimeError represents interpreter/runtime failures. Parse and compile errors happen before execution and cannot be caught by the script being compiled.

Read a diagnostic

Runtime traces list the innermost failing call first, then its callers. Frames include the call's source location; closures appear as <anonymous>. Imported functions, methods, constructors, accessors, and pipeline callbacks participate. Native frames are marked [native], with a Rust dispatch boundary separate from the failure's source location. Earlier successful calls are not part of the trace.

The original failure stays at the top. Parse/compile diagnostics precede runtime and have no runtime call frames. A two-binding catch exposes a CallStack value alongside the error; report_error can render that context without executing an external reporting command.

Cleanup without hiding the original failure

let $closed = false
try
{
    try { throw RuntimeError(what: "example", message: "stop") }
    finally { $closed = true }
}
catch ($problem) { echo "handled" }
echo $closed
handled
true

finally runs when its protected block finishes, returns, breaks, continues, or fails. Keep cleanup focused on local state and resource closing. External commands, process suspension, and control transfers out of cleanup are rejected; that includes an external echo. Use report_error $stack $error for native reporting where appropriate.

If both the body and cleanup fail, the original failure remains primary and the cleanup failure is retained as secondary context. Explicit resource ownership makes cleanup predictable; see Bytes and I/O.


Related: Named unions · Bytes and I/O · HTTP

Clone this wiki locally