Runtime errors and stack traces

Dune distinguishes recoverable values from failures that abort the current VM execution. A recoverable operation returns a choice such as outcome.Outcome<T, E>; a panic is reserved for violated invariants and operations that cannot continue, such as division by zero or indexing outside an array.

Explicit panic

The runtime module exposes the only native primitive used by Dune's standard library:

import runtime;

fn require_positive(value: int): unit {
    if value <= 0 {
        runtime.panic("value must be positive");
    }
}

runtime.panic(message) has the panic category and preserves message verbatim. It aborts the current script, test, REPL entry, or notebook cell after running all pending defer cleanups.

Categories

Every VM failure has a stable category:

CategoryMeaning
panicAn explicit runtime.panic call.
bounds errorInvalid array, tuple, record, text, or slice position.
arithmetic errorAn arithmetic operation cannot produce a value, such as division by zero.
runtime type errorInvalid operands reached the VM or a dynamic callable had the wrong shape.
I/O errorA non-recoverable stream/runtime I/O failure. Normal filesystem APIs return Outcome values.
foreign function errorA C FFI symbol or invocation is unsupported or invalid.
VM errorA bytecode invariant failed. This normally indicates a compiler or VM bug.

The category is available to C++ embedders as RuntimeErrorKind; the original message, frames, and deferred-cleanup failures remain structured on RuntimeError instead of requiring consumers to parse what().

Stack trace format

The compiler records the source span of every bytecode instruction. When an operation fails, the VM snapshots all live Dune frames before unwinding them:

panic: value must be positive
stack trace:
  0: require_positive
      at examples/check.dn:5:9
  1: validate
      at examples/check.dn:10:5
  2: <top-level>
      at examples/check.dn:13:1

Frames are ordered from the failing function to the outermost caller. Imported functions use their module-qualified names and point to the imported .dn file, including pure-Dune standard-library modules. Lambdas use a generated <lambda@line:column> name.

dune test names the outer frame as test "name" for both test blocks and @test functions. REPL entries use <repl>. Notebook frames use <path>.dnb#cell-<id> and report lines relative to that cell, so a function defined in one cell and called in another points to both cells correctly.

Panic during defer

The first failure remains primary. Dune still runs every pending cleanup in LIFO order. Each cleanup failure is appended as while running deferred cleanup: ... and keeps its own stack trace; it does not replace the original category, message, or frames.

Recoverable errors remain values

Returning Failed(error) from outcome.Outcome<T, E> does not create a stack trace and is not caught by the panic machinery. It remains a normal choice value that can be matched, inspected, or propagated with ?. Use Outcome for expected failures and runtime.panic for conditions the current execution cannot safely continue from.