# 错误处理 Deft 的错误就是值 —— 具体来说是带标签的 `%Err{ :message ... }` 映射。本页介绍 `try`/`catch`、`throw`、Result 类型(`%Ok`/`%Err`),以及用于串联返回 Result 的 代码的组合子与传播形式。 > 设计说明: Ok/Err 是普通的带标签映射。它们原封不动地流经 `set`、 > 函数参数和管道 —— 不会自动解包,也不会隐式短路。如何处理 Result > 始终由调用方决定。显式工具(`err!`、`and-then`、`unwrap`、`match`) > 负责解包。 ## 三种工具 | 工具 | 使用时机 | |---|---| | `try` / `catch` | 你可以在本地从失败中恢复。 | | Result(`%Ok` / `%Err`) | 失败是一个值 —— 由调用方决定如何处理。 | | `throw` | 调用方不应被期望处理的真正异常状况。 | ## `try` / `catch` `try { body } catch name { handler }` 捕获任何抛出的错误,并将其绑定到 `name`:
def safe-read {|path^string|
    try {
        set raw [unwrap [fs/read $path]]
        [json/parse $raw]
    } catch e {
        echo "failed: $e~message"
        %{}
    }
}
捕获到的值是一个 `%Err{ :message ... }` 映射。用 `~` 访问其字段。 ### 块中的多条语句 `try` 和 `catch` 块都可以包含任意数量的语句;最后一条语句的值就是该块的值:
try {
    set raw [unwrap [fs/read $path]]
    set parsed [json/parse $raw]
    parsed~users
} catch e {
    log/error "parse failed" $e
    @{}
}
### 重新抛出
try {
    [dangerous-op]
} catch e {
    if ($e~message == "transient") {
        [retry]                            # swallow and retry
    } else {
        [throw $e]                         # re-throw
    }
}
## `throw` `throw` 抛出一个 `%Err`。在 `try` 内部,它会被捕获;在外部,它会中止脚本 (在 TUI 宿主中则是中止面板的应用线程)。
def divide {|a b|
    if ($b == 0) { [throw "division by zero"] }
    ($a / $b)
}
你可以抛出一个字符串(成为 `%Err` 的 `:message`),也可以抛出带额外字段的 完整 `%Err` 映射:
[throw %Err{ :message "invalid input" :code 422 :field "email" }]
## Result 类型 对于可预测、可恢复的失败,优先使用 Result 类型而不是异常。Result 要么是 `%Ok{ :value ... }`,要么是 `%Err{ :message ... }`。它们是普通的带标签 映射 —— 原封不动地流经 `set`、函数参数和管道。 ### 构造 Result
%Ok{ value 42 }                  # literal tagged-map construction
%Err{ message "nope" }

[ok 42]                          # constructor functions (equivalent)
[err "nope"]
当需要包装一个计算出的值时,`[ok]` 和 `[err]` 很方便:
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
        }
    }
}
### 谓词
[ok? $r]                                     # true if %Ok
[err? $r]                                    # true if %Err
### 解包 `unwrap` 提取 `%Ok` 的值,或为 `%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
要进行显式的分支处理,请使用 `match`:
match $r {
    %Ok{ value $v }   => [process $v]
    %Err{ message $m} => [log/error $m]
}
## 标记返回 Result 的函数 ### `def name^Result`(受检查) 显式的返回类型标注。编译器会把该约定记录在闭包上;在运行时,每条返回路径 都会被检查 —— 如果返回的值不是 `%Ok` 或 `%Err`,就会向 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 }
}
该检查是运行时(而非静态)的 —— 当执行真正到达有问题的返回处时才会触发。 对未标注的函数开销很小(每个返回处一个分支,用于跳过检查)。目前 `^Result` 是唯一被强制执行的返回类型;其他标注(`^string`、`^number`)仅作文档用途。 ## 串联返回 Result 的操作 两种互补的风格:`ok!` 用于命令式序列,`result/*` 组合子用于函数式管道。 ### `ok!` 和 `err!` 断言函数 这一对标准库函数会断言 Result 的变体,并提取其载荷或从外层函数提前返回: | 形式 | `%Ok` | `%Err` | 非 Result | |---|---|---|---| | `[ok! expr]` | 解包为 `:value` | 提前返回 Err | 原样传递 | | `[err! expr]` | 提前返回 Ok | 提取 `:message` | 原样传递 | `ok!` 是推进正常流程的代码的常用形式 —— “我期望这一步成功;把值给我, 否则就退出。”`err!` 是它在错误处理路径上的对称对应 —— “我期望这一步已经 失败;把消息给我。” 两者各有两种等价的语法形式:
[ok! [fs/read $path]]            # bracket form
[fs/read $path](ok!)             # postfix form — same semantics

[err! $r]                        # bracket form
[$r](err!)                       # postfix form
后缀形式读起来很自然,即“对结果调用 ok!” —— 在调用处就可见,不会因为 行尾的小符号而看漏。
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"]
}
两者都可以直接作用于变量 —— 无需包装:
def process^Result {|r|
    set v [ok! $r]                          # unwraps Ok, propagates Err
    ($v + 1)
}
`ok!` 和 `err!` 是编译器内建函数(intrinsic),不是普通函数 —— 它们不能被 重定义。提前返回语义需要编译器支持(运行时函数无法退出其调用方)。如果只 想要纯粹的解包而不提前退出,请改用 `[unwrap $r]` 或 `[unwrap $r default]`。 ### 标准库组合子(函数式风格) | 形式 | 用途 | |---|---| | `[result/map $r $f]` | 将纯函数提升到 Result 中:`Ok(v)` → `Ok(f(v))`,Err 会传播 | | `[and-then $r $f]` | 单子绑定(monadic bind,即 and-then):`Ok(v)` → `f(v)`,Err 会传播。`f` 必须返回 Result | | `[result/traverse $coll $f]` | 在集合上映射 `f`,遇到第一个 Err 时短路。返回 `Ok([values])` | | `[result/sequence $coll-of-results]` | 将 `[Result a]` 变成 `Result [a]`,遇到第一个 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!` 通常更清晰;对于函数式管道,`and-then` 和 `result/traverse` 通常更清晰。 ## 我该用哪一种? ### 本地恢复 → `try` / `catch`
def parse-or-default {|raw|
    try { [json/parse $raw] } catch _e { %{} }
}
### 把决定权上交 → Result
def fetch-user^Result {|id^string|
    # caller decides how to handle %Ok vs %Err
    [http/get "$api/users/$id"]
}
### 首次失败即退出 → `ok!`
def load-config^Result {|path^string|
    set raw [ok! [fs/read $path]]
    set parsed [ok! [json/parse $raw]]
    %Ok{ value $parsed }
}
### 校验集合 → `result/traverse`
def validate-all^Result {|inputs|
    [result/traverse $inputs {|x| [validate $x]}]
}
### 真正的异常 → `throw`
def assert-positive {|n^number|
    if ($n < 0) { [throw "expected positive, got $n"] }
    $n
}
## 约定 - 大多数可能失败的标准库调用都会返回 Result。用 `err?`、`unwrap`、 `match` 或 `err!` 检查。 - 违反不变式(程序错误)时抛出异常;预期内的失败(用户输入、网络等)返回 `%Err`。 - 用 `^Result` 标注返回 Result 的函数,以便运行时检查器能发现遗漏的包装。 - 如果要处理多种错误类型,请在针对 `%Err` 的 `match` 中包含 `_` 分支 —— 无匹配时 `match` 会抛出异常。 - `try` 块与 `defer` 能很好地组合 —— 即使抛出异常,延迟清理也会运行。 ## 快速参考 | 形式 | 用途 | |---|---| | 构造 | | | `[ok value]` / `%Ok{ value ... }` | 构造一个 Ok 结果 | | `[err message]` / `%Err{ message ... }` | 构造一个 Err 结果 | | 检查 | | | `[ok? x]`, `[err? x]` | Result 谓词 | | `[unwrap result ?default]` | 获取内部值(不提前退出) | | `match $r { %Ok{ value $v } => ... %Err{ message $m } => ... }` | 解构 Result | | 标记 | | | `def name! {|args| ...}` | 非正式的 Result/可变标记(仅文档用途) | | `def name^Result {|args| ...}` | 受检查的返回 Result 约定(违反时警告) | | 传播 | | | `[ok! expr]` 或 `[expr](ok!)` | 断言 Ok:解包 `:value` 或提前返回 Err | | `[err! expr]` 或 `[expr](err!)` | 断言 Err:提取 `:message` 或提前返回 Ok | | `[result/map $r $f]` | 将纯函数 `f` 提升到 Result | | `[and-then $r $f]` | 单子绑定(and-then) | | `[result/traverse $coll $f]` | 在集合上映射并短路 | | `[result/sequence $coll]` | `[Result]` → `Result [...]` | | 异常 | | | `[throw value]` | 抛出一个错误 | | `try { ... } catch e { ... }` | 捕获任何抛出的错误 |