137 lines
7.2 KiB
Markdown
137 lines
7.2 KiB
Markdown
# 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<T, E>`,区分 `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。章节编号沿用原清单。
|
||
|