This guide covers the Turmeric error handling story: Result<T, E>, panic, must!, and guidance on when to use each.
Turmeric offers two error handling strategies:
Result<T, E> — recoverable errors; use when callers should handle failure.panic — unrecoverable errors; use for programming mistakes and invariant violations.Result<T, E>A result value is either (ok value) or (err error). The underlying representation is a heap-allocated struct { bool is_ok; int64_t ok_val; int64_t err_val; } returned as ptr<void>.
(ok 42) ;; Result<int, _>: success
(err 99) ;; Result<_, int>: failure
(ok? r) ;; => bool
(err? r) ;; => bool
(ok-val r) ;; => int (unsafe; check ok? first)
(err-val r) ;; => int (unsafe; check err? first)
(result-unwrap r) ;; unwrap or print error to stderr and return 0
(result-unwrap-or r default) ;; unwrap or return default
(result-expect r "message") ;; unwrap or panic with message
(result-must r) ;; unwrap or panic with default message
(result-must-msg r "msg") ;; unwrap or panic with custom message
(result-map r f) ;; apply f to ok value
(result-map-err r f) ;; apply f to err value
(result-flat-map r f) ;; f returns a result; chain
(result-or r alternative) ;; return r if ok, else alternative
(result-or-else r f) ;; f produces alternative on err
(result-collect vec) ;; (vec result) -> result<vec, E>; first err wins
(result-partition vec) ;; -> pair; use result-partition-ok / result-partition-err
(result-partition-ok pair) ;; -> vec of ok values
(result-partition-err pair) ;; -> vec of err values
(ok-or opt err-val) ;; option -> result
(err-context r context-str) ;; wrap error with additional context string
panicpanic terminates the program unconditionally. Use it for:
(panic "something went wrong")
This prints panic: something went wrong to stderr and calls abort().
If tur_panic is called during a panic (e.g. in a destructor/defer chain), it prints double panic: aborting and calls abort() immediately.
must! and must-msg!These macros unwrap an option or result, panicking on failure:
(must! (some 42)) ;; => 42
(must! (none)) ;; panic: option-must: called on none
(must-msg! (some 42) "expected a value") ;; => 42
(must-msg! (none) "expected a value") ;; panic: expected a value
For result, use the function forms directly:
(result-must (ok 7)) ;; => 7
(result-must (err 99)) ;; panic: result-must: called on err
(result-must-msg (ok 7) "failed") ;; => 7
(result-must-msg (err 99) "failed") ;; panic: failed
option-must and option-expect(option-must (some 42)) ;; => 42
(option-must (none)) ;; panic: option-must: called on none
(option-expect (some 42) "want value") ;; => 42
(option-expect (none) "want value") ;; panic: want value
ignore!Explicitly discard a result to suppress unused-result warnings (when the linter is enabled):
(ignore! (some-fn-returning-result))
All error types are in stdlib/exn.tur:
| Type | Fields | Constructor |
|---|---|---|
Error |
message: cstr, cause: ptr<void> |
(Error. msg cause) |
IoError |
message: cstr, errno_: int |
(IoError. msg errno) |
ParseError |
message: cstr, line, col, file: cstr |
(ParseError. msg line col file) |
ValidationError |
message: cstr, field: cstr |
(ValidationError. msg field) |
NotFoundError |
what: cstr |
(NotFoundError. what) |
PermissionError |
message: cstr, path: cstr |
(PermissionError. msg path) |
| Situation | Approach |
|---|---|
| Caller might handle the failure (e.g. file not found) | result |
| Programming error / violated invariant | panic |
| Unwrapping a result you are confident is ok | result-must / must! |
| Optional value that must be present | option-must / must! |
| Discarding a result intentionally | ignore! |
The following features are planned but not yet implemented:
? operator — Phase R1; short-circuit error propagation.catch-unwind — Phase R2; catch panics at a boundary.--warn-unused-result compiler flag — Phase R6.--lint-panic compiler flag — Phase R6.When a panic occurs inside an effect handler or continuation:
Effect handlers: Panics that occur during effect handling are caught by the
catch-unwind boundary of the with-handler form. If no catch-unwind is present,
the panic propagates normally through the effect handler chain.
Continuations: Panics that occur in code that has captured a continuation via
shift/reset will unwind the stack up to the continuation boundary. If the
continuation is resumed after a panic, the panic state is cleared and normal execution
continues. This means (shift k ...) forms do NOT automatically re-panic on resume.
Defer: Defer thunks are fired during panic unwinding (Phase 17 mechanism).
If a defer thunk itself panics, the double-panic guard triggers abort().
v1 behavior: In the current implementation, (async fn) calls the async function
directly and synchronously (no actual fibers or scheduler suspension). A panic inside an
async body propagates normally through the call stack — it is NOT caught at the task
boundary. If the panic is unhandled it terminates the process.
Similarly, a rejected future (one settled via tur_future_reject) causes abort() when
awaited; it does not surface as Err at the await site.
v2 target behavior (for reference when true fiber-based async lands):
Panic in async task: A panic inside an async task will be caught at the task
boundary via an implicit catch-unwind. The task's future will resolve to a rejected
state carrying the panic payload as Err(PanicPayload). Use catch-unwind at the
join point to recover.
Panic during task cancellation: If a task is cancelled while a panic is in progress, the panic takes precedence; cancellation is a no-op and the panic propagates.
Panic in async main: An uncaught panic terminates the program with a nonzero exit code after all defer thunks have fired.
WASM target: Panics lower to the WebAssembly unreachable instruction (traps
execution). Deferred until a WASM codegen backend is added.
Defer inside async: Defer thunks registered inside an async block fire in reverse order during the normal unwind or panic unwind of that async block, consistent with Phase 17 defer semantics.