这是一篇关于 Rust 语言中错误处理的博客文章,主要讨论了unwrap()
的使用场景、 panic 的含义及作用、错误处理的方式等内容,具体如下:
- 立场阐述:在应用和库中不应将 panic 用于错误处理;在原型开发、测试、基准测试和文档示例中使用 panic 进行错误处理可能是可以接受的;Rust 程序 panic 表示程序中有 bug;可以将 blame 分配给 panic 的责任方;无法将所有不变量都移入类型系统时,对于运行时不变量有几种选择;在某些情况下可以使用
unwrap()
、expect()
等;更倾向于使用expect()
,但在expect()
会产生噪音时使用unwrap()
。 unwrap()
介绍:unwrap()
是定义在Option<T>
和Result<T, E>
上的方法,分别在Some
或Ok
变体时返回底层的T
,否则 panic。其目的是解决是否以及在多大程度上使用unwrap()
的问题。- panic 的含义:panic 发生时,进程可能会终止或进行栈展开,如果栈展开未被捕获,程序将终止并打印 panic 消息和源信息;可以捕获 panic 并进行处理,如在 Web 服务器和测试 harness 中;panic 对于程序员调试很有用,但对终端用户不太友好。
- 错误处理介绍:Rust 中有多种处理错误的方式,如以非零退出码终止、panic 或使用
Result<T, E>
处理正常值;anyhow
crate 可轻松为错误值添加上下文;在 Rust 中,将错误作为值处理(使用Result<T, E>
)通常被视为最佳实践,标准库和核心生态库都使用这种方式。 unwrap()
是否应用于错误处理:在一些场景下常见使用unwrap()
,如快速一次性程序、测试和文档示例;个人对于在这些场景中使用unwrap()
没有强烈意见,但认为在 Rust 库或应用中不应使用unwrap()
进行错误处理,因其会导致不良的用户体验;另外,unwrap()
在某些程序员临时用于解决问题后会被删除并添加“ proper ”错误处理。- 可恢复与不可恢复错误:Rust 书籍中“Error Handling”章节提出将错误分为可恢复和不可恢复,但作者认为这种概念化没有帮助,应根据具体情况确定是将错误作为值处理还是作为 panic 处理,如用户指定路径的文件未打开是错误,而程序从静态字符串字面量构建正则表达式出错则应 panic。
- 是否永远不应 panic:一般来说,正确的 Rust 程序不应 panic;对于快速的 Rust“脚本”中使用 panic 进行错误处理,这是否正确存在争议,可视为有标记为
wontfix
的 bug。 - 是否永远不应使用
unwrap()
或expect()
:unwrap()
和expect()
只有在值不符合调用者预期时才会 panic,如果值总是符合预期则不会 panic;很多关于unwrap()
的困惑来自于人们对其使用的误解,实际上是关于是否将 panic 作为错误处理策略的问题。 - 运行时不变量:运行时不变量是在运行时应始终为真的保证,可通过不同方式设置,如使用
std::num::NonZeroUsize
在编译时保证,或使用Option<usize>
在运行时由调用者保证。 - 为何不将所有不变量都设为编译时不变量:有时无法将所有不变量都移至编译时,有时是因为 API 复杂性,如
aho-corasick
crate 中的示例,通过在运行时进行检查和 panic 可以保持 API 的简单性,避免引入过多的类型和复杂性。 - 当不变量无法移至编译时:以确定性有限自动机(DFA)的搜索实现为例,由于输入可能是任意正则表达式,无法将 DFA 构建和搜索的不变量推至编译时,必须在运行时维护,若 panic 发生则意味着代码存在 bug。
- 为何不返回错误而不是 panic:将错误作为返回值处理可以避免 panic,但会使代码更复杂,文档更混乱,这种编码风格在高可靠性领域可能有一定价值,但会限制使用标准库和核心生态库,且难以正确记录错误条件。
- 何时应使用
unwrap()
即使不是必要的:以regex-syntax
crate 中的示例为例,有时使用unwrap()
可以使代码更简单,虽然可以避免使用unwrap()
,但改写后的代码更复杂,在明显可以判断unwrap()
不会 panic 的情况下,使用unwrap()
更合适。 - 为何不使用
expect()
而使用unwrap()
:expect()
在unwrap()
的基础上添加了额外的上下文信息到 panic 消息中,但消息往往较短,且不一定包含使用expect()
正确的全部理由;在某些情况下,使用expect()
会增加代码的噪音,应根据具体情况判断使用unwrap()
还是expect()
。 - 是否应针对
unwrap()
的使用进行 lint:反对对unwrap()
的使用进行 lint 的观点包括:很多expect()
的使用会增加代码噪音;unwrap()
是 idiomatic 的,在标准库和核心生态库中广泛使用;还有很多其他可能 panic 的常见情况无法通过 lint 禁止;禁止unwrap()
可能导致人们使用更无意义的expect("")
;虽然不能完全反对 lint,但认为其带来的好处不足以抵消负面影响。 - panic 为何如此重要:panic 是 bug 常常不需要在调试器中运行 Rust 程序的原因之一,它提供栈跟踪和行号,对于终端用户也很有用,可轻松获取完整栈跟踪;应在合适的地方使用 panic,如使用
assert!
检查前提条件和运行时不变量,使用expect()
添加上下文,使用unwrap()
避免噪音等;将运行时不变量推至编译时不变量通常是更好的选择,但有时无法或不适合这样做。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。