# Error Handling Deft errors are values — specifically tagged `%Err{ :message ... }` maps. This page covers `try`/`catch`, `throw`, the Result type (`%Ok`/`%Err`), and the combinators + propagation forms for chaining Result-returning code. > **Design note:** Ok/Err are ordinary tagged maps. They flow through `set`, > function args, and pipelines *unchanged* — no auto-unwrap, no implicit > short-circuiting. The caller always decides what to do with a Result. > Explicit tools (`err!`, `and-then`, `unwrap`, `match`) do the unwrapping. ## Three Tools | Tool | Use when | |---|---| | `try` / `catch` | You can recover from the failure locally. | | Result (`%Ok` / `%Err`) | The failure is a value — the caller decides what to do. | | `throw` | A genuinely exceptional condition the caller can't be expected to handle. | ## `try` / `catch` `try { body } catch name { handler }` catches any thrown error and binds it to `name`:
 def safe-read {|path^string|
    try {
        set raw [fs/read $path]
        [json/parse $raw]
    } catch e {
        echo "failed: $e~message"
        %{}
    }
} 
The caught value is a `%Err{ :message ... }` map. Access its fields with `~`. ### Multiple statements in a block Both `try` and `catch` blocks may contain any number of statements; the value of the last statement is the block's value:
 try {
    set raw [fs/read $path]
    set parsed [json/parse $raw]
    parsed~users
} catch e {
    log/error "parse failed" $e
    @{}
} 
### Re-throwing
 try {
    [dangerous-op]
} catch e {
    if ($e~message == "transient") {
        [retry]                            # swallow and retry
    } else {
        [throw $e]                         # re-throw
    }
} 
## `throw` `throw` raises an `%Err`. Inside a `try`, it's caught; outside, it aborts the script (or the panel's app thread, in a TUI host).
 def divide {|a b|
    if ($b == 0) { [throw "division by zero"] }
    ($a / $b)
} 
You can throw a string (becomes the `:message` of an `%Err`) or a full `%Err` map with extra fields:
 [throw %Err{ :message "invalid input" :code 422 :field "email" }] 
## Result Type For predictable, recoverable failures, prefer the Result type over exceptions. A Result is either `%Ok{ :value ... }` or `%Err{ :message ... }`. These are ordinary tagged maps — they flow through `set`, function args, and pipelines unchanged. ### Constructing Results
 %Ok{ value 42 }                  # literal tagged-map construction
%Err{ message "nope" }

[ok 42]                          # constructor functions (equivalent)
[err "nope"] 
`[ok]` and `[err]` are convenient when wrapping a computed value:
 def safe-parse {|raw^string|
    if [empty? $raw] {
        %Err{ message "empty input" }
    } else {
        try {
            %Ok{ value [json/parse $raw] }
        } catch e {
            $e                                # already an %Err
        }
    }
} 
### Predicates
 [ok? $r]                                     # true if %Ok
[err? $r]                                    # true if %Err 
### Unwrapping `unwrap` extracts the value of an `%Ok`, or returns a default for `%Err`:
 set v [unwrap $r 0]                          # the value if Ok, 0 if Err
set v [unwrap $r]                            # the value if Ok, the message if Err 
For explicit branch handling, use `match`:
 match $r {
    %Ok{ value $v }   => [process $v]
    %Err{ message $m} => [log/error $m]
} 
## Marking Result-returning Functions ### `def name^Result` (checked) An explicit return-type annotation. The compiler records the contract on the closure; at runtime, every return path is checked — if the returned value isn't `%Ok` or `%Err`, a warning is logged to stderr.
 def load-config^Result {|path^string|
    if [nil? $path] { return [err "no path"] }
    %Ok{ value $path }
}

def buggy^Result {|x|
    if ($x == 0) { return "zero" }   # ⚠ warns: ^Result fn returned string
    %Ok{ value $x }
} 
The check is runtime (not static) — it fires when execution actually reaches the offending return. Cheap on un-annotated functions (one branch per return to skip the check). Currently `^Result` is the only enforced return type; other annotations (`^string`, `^number`) are documentation only. ## Chaining Result-returning Operations Two complementary styles: `ok!` for imperative sequences, `result/*` combinators for functional pipelines. ### The `ok!` and `err!` assert functions Two paired stdlib functions that *assert* a Result's variant and either extract the payload or early-return from the enclosing function: | Form | `%Ok` | `%Err` | non-Result | |---|---|---|---| | `[ok! expr]` | unwraps to `:value` | early-returns the Err | passthrough | | `[err! expr]` | early-returns the Ok | extracts `:message` | passthrough | `ok!` is the common form for forward-progress code — "I expect this to succeed; give me the value, or bail." `err!` is its symmetric counterpart for error-handling paths — "I expect this to have failed; give me the message." Each has two equivalent syntactic shapes:
 [ok! [fs/read $path]]            # bracket form
[fs/read $path](ok!)             # postfix form — same semantics

[err! $r]                        # bracket form
[$r](err!)                       # postfix form 
The postfix form reads naturally as "call ok! on the result" — visible at the call site, no tiny glyph to miss at end-of-line.
 def load-config^Result {|path^string|
    set raw [ok! [fs/read $path]]           # unwrap Ok, or propagate Err
    set parsed [ok! [json/parse $raw]]
    %Ok{ value $parsed }
}

def report-failure^Result {|r|
    set msg [err! $r]                       # extract Err message, or propagate Ok
    [log "got error: $msg"]
} 
Both work on variables directly — no wrapping required:
 def process^Result {|r|
    set v [ok! $r]                          # unwraps Ok, propagates Err
    ($v + 1)
} 
`ok!` and `err!` are compiler intrinsics, not regular functions — they can't be redefined. The early-return semantics require compiler support (a runtime function can't exit its caller). For pure unwrap without early-exit, use `[unwrap $r]` or `[unwrap $r default]` instead. ### Stdlib combinators (functional style) | Form | Purpose | |---|---| | `[result/map $r $f]` | Lift a pure function into Result: `Ok(v)` → `Ok(f(v))`, Err propagates | | `[and-then $r $f]` | Monadic bind (and-then): `Ok(v)` → `f(v)`, Err propagates. `f` must return a Result | | `[result/traverse $coll $f]` | Map `f` over a collection, short-circuit on first Err. Returns `Ok([values])` | | `[result/sequence $coll-of-results]` | Turn `[Result a]` into `Result [a]`, short-circuit on first Err |
 # bind: chain Result-returning steps
set r [and-then [op-a $x] {|a| [and-then [op-b $a] {|b| [ok ($a + $b)]}]}]

# traverse: validate a list, bail on first failure
set r [result/traverse $inputs {|x| [validate $x]}]
match $r {
    %Ok{ value $validated } => [process-all $validated]
    %Err{ message $m }      => [log/error $m]
} 
`ok!` is usually cleaner for imperative sequences; `and-then` and `result/traverse` are usually cleaner for functional pipelines. ## Which Should I Use? ### Recover locally → `try` / `catch`
 def parse-or-default {|raw|
    try { [json/parse $raw] } catch _e { %{} }
} 
### Pass the decision up → Result
 def fetch-user^Result {|id^string|
    # caller decides how to handle %Ok vs %Err
    [http/get "$api/users/$id"]
} 
### Bail on first failure → `ok!`
 def load-config^Result {|path^string|
    set raw [ok! [fs/read $path]]
    set parsed [ok! [json/parse $raw]]
    %Ok{ value $parsed }
} 
### Validate a collection → `result/traverse`
 def validate-all^Result {|inputs|
    [result/traverse $inputs {|x| [validate $x]}]
} 
### Genuinely exceptional → `throw`
 def assert-positive {|n^number|
    if ($n < 0) { [throw "expected positive, got $n"] }
    $n
} 
## Conventions - Most stdlib calls that can fail return a Result. Inspect with `err?`, `unwrap`, `match`, or `err!`. - Throw on invariant violations (programmer error); return `%Err` on expected failures (user input, network, etc.). - Annotate Result-returning functions with `^Result` so the runtime checker can catch missing wraps. - Include a `_` branch in `match` against `%Err` if you want to handle multiple error kinds — `match` throws on no-match. - `try` blocks compose cleanly with `defer` — deferred cleanups run even when an exception is thrown. ## Quick Reference | Form | Purpose | |---|---| | **Construction** | | | `[ok value]` / `%Ok{ value ... }` | Construct an Ok result | | `[err message]` / `%Err{ message ... }` | Construct an Err result | | **Inspection** | | | `[ok? x]`, `[err? x]` | Result predicates | | `[unwrap result ?default]` | Get the inner value (no early-exit) | | `match $r { %Ok{ value $v } => ... %Err{ message $m } => ... }` | Destructure Result | | **Marking** | | | `def name! {|args| ...}` | Informal Result/mutation marker (documentation only) | | `def name^Result {|args| ...}` | Checked Result-returning contract (warns on violation) | | **Propagation** | | | `[ok! expr]` or `[expr](ok!)` | Assert Ok: unwrap `:value` or early-return the Err | | `[err! expr]` or `[expr](err!)` | Assert Err: extract `:message` or early-return the Ok | | `[result/map $r $f]` | Lift pure `f` into Result | | `[and-then $r $f]` | Monadic bind (and-then) | | `[result/traverse $coll $f]` | Map+short-circuit over a collection | | `[result/sequence $coll]` | `[Result]` → `Result [...]` | | **Exceptions** | | | `[throw value]` | Raise an error | | `try { ... } catch e { ... }` | Catch any thrown error |