# 10 · 错误与资源 0/16 项已确认。当前推进 10-01;本分类尚未完成选择。 本页整理原讨论的问题、验收要求和候选方案。勾选代表讨论中已确认的规则,不代表编译器或运行时已经实现。 ## 10-01 失败的基本控制流 - [ ] 待讨论 确定使用错误结果、异常还是混合机制,以及失败后调用处如何继续。 ## 10-02 错误的表示与分类 - [ ] 待讨论 确定错误携带哪些信息、如何区分类别和匹配处理器,是否允许自定义错误。依赖【对象与类型模型】。 ## 10-03 失败是否属于接口契约 - [ ] 待讨论 确定函数是否声明可能的失败、调用者是否必须处理,以及遗漏处理如何检查。依赖【类型系统】。 ## 10-04 向上传播与机制转换 - [ ] 待讨论 确定自动或显式传播规则,以及错误结果与异常之间是否允许转换、转换保留什么信息。 ## 10-05 捕获与恢复 - [ ] 待讨论 确定处理器的匹配顺序、可见范围、重新传播,以及处理后从哪里继续;明确是否支持回到失败点继续执行。 ## 10-06 退出作用域与栈展开 - [ ] 待讨论 确定错误跨越调用和作用域时的退出顺序,哪些清理必须执行,以及清理能访问哪些仍有效的数据。 ## 10-07 清理结构与执行时机 - [ ] 待讨论 决定支持哪些 `finally`、`defer` 或资源作用域机制,明确注册时机、执行顺序,以及正常结束、提前返回和错误退出的行为。 ## 10-08 资源责任与关闭规则 - [ ] 待讨论 确定谁负责关闭、责任能否转移,以及共享、重复关闭和关闭后使用如何处理。依赖【所有权与生命周期/类型系统】。 ## 10-09 清理过程中再次失败 - [ ] 待讨论 确定原始错误与清理错误如何保留,后续清理是否继续,以及最终向外报告什么。 ## 10-10 析构、终结与 GC 的边界 - [ ] 待讨论 确定是否提供析构或终结机制、执行保证及限制,明确内存回收能否承担资源关闭责任。依赖【内存管理与运行时】。 ## 10-11 不可恢复失败与终止 - [ ] 待讨论 区分可处理失败、程序缺陷和强制终止;确定各类情况下是否展开和清理,以及未处理错误到达程序边界后的行为。 ## 10-12 异步操作与取消中的清理 - [ ] 待讨论 明确是否支持异步清理、取消如何传播,以及清理期间再次取消的行为。依赖【并发与异步】。 ## 10-13 并发中的多个失败 - [ ] 待讨论 确定任务失败由谁接收,多个失败如何组合,其他任务是否继续或取消。依赖【并发与异步】。 ## 10-14 资源协议与外部接口边界 - [ ] 待讨论 明确语言内置保证与库协议的分工,以及外部调用的错误如何进入本语言、跨越外部调用时谁负责清理。依赖【定制协议、标准库与外部互操作】。 ## 10-15 错误诊断与上下文保留 - [ ] 待讨论 确定是否保留调用位置、传播路径、原始原因与附加上下文,以及包装、重抛后保留什么。 ## 10-16 跨分类一致性验收 - [ ] 待讨论 用正常返回、嵌套失败、清理失败、未处理失败及适用的取消场景核对全部规则,消除与其他分类决定的冲突和阻塞。 ## 当前问题的方案与语言对照 以下沿用原讨论材料;建议和参考语言的做法均不自动成为本语言规则。 当前仍是 **10-01:常规操作失败时,返回失败结果,还是直接中断控制流?** 这里先讨论文件不存在、配置格式错误等情况,致命故障留到 10-11。 **相关语言怎样做** | 语言 | 规则与小例子 | 好处、代价与启发 | |---|---|---| | **Python** | 广泛使用异常。例如 `int("abc")` 抛出 `ValueError`,跳过当前路径中的后续语句,寻找匹配的 `except`;中间函数不必逐层转发。也允许接口返回普通错误结果。[官方文档](https://docs.python.org/3/tutorial/errors.html) | 正常路径紧凑,便于外层集中处理;调用表达式本身通常看不出可能抛什么。启发:选择异常,就要接受并管理这种隐式退出路径。 | | **JS** | 支持 `throw` 和 `try/catch`,可抛出任意值。例如 `throw "bad config"` 会中断当前路径,由外层捕获。[语言规范](https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-throw-statement) | 传播方便,但处理器不能天然假定拿到的是标准错误对象。启发:**采用异常**与**允许抛什么**是两个独立决定。 | | **TS** | 运行时沿用 JS 的异常行为。静态检查可以约束错误的使用:启用 `useUnknownInCatchVariables` 时,捕获值默认为 `unknown`,访问其属性前需要缩小类型;这不等于静态列出函数可能抛出的错误。[官方文档](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#defaulting-to-the-unknown-type-in-catch-variables---useunknownincatchvariables) | 能提高处理代码的安全性,却不会自动消除隐式异常路径。启发:静态类型检查与失败控制流要分别设计。 | | **Go** | 常规错误通常作为额外返回值,例如得到 `(文件, error)` 后检查 `err != nil`;返回错误本身不会自动退出调用者。另有 `panic/recover` 机制。[官方说明](https://go.dev/doc/effective_go#errors) | 检查与返回位置明确;逐层传播容易重复,也可能忽略错误。启发:显式错误值和另一种中断机制可以并存,但需要明确用途。 | | **Rust** | 常规可恢复错误使用 `Result`,区分 `Ok` 与 `Err`。例如 `File::open(path)?` 在失败时提前返回当前函数;不用 `?` 时,可检查或传递这个结果。[官方文档](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html) | 结果结构清楚,传播简写减少重复;需要相应的类型与转换规则。启发:**显式结果并不要求每次手写完整分支**,传播语法可以另行设计。 | 这些实践提供参考,不替我们作决定;表中的好处与代价是取舍分析,不是对历史设计动机的断言。 **我们当前的三个候选方案** 沿用“配置文件不存在就使用默认配置,格式错误就停止启动”的例子: - **A.常规失败统一返回显式结果**:调用者检查或显式传播。可以随后讨论传播简写,无须现在确定类型语法。 - **B.常规失败统一通过异常传播**:调用者捕获“文件不存在”来恢复,格式错误继续向外传播。 - **C.两者并存**:允许不同接口选择,但必须进一步确定分工,避免调用者不清楚该检查返回值还是捕获异常。 我的暂时建议是优先考虑 **A**:它让失败路径容易在调用处辨认,也能通过传播简写减少重复。如果你更重视多层调用后集中处理错误,**B** 更值得选。选择 **C** 则需要有明确的两类使用场景,收益才足以抵消复杂度。语言用途尚未确定,这个建议仍是候选。 你更倾向 **A、B,还是 C**? ## 讨论来源 《语言设计 10:错误与资源》,整理日期 2026-10-28。章节编号沿用原清单。