7.0. 展开
原文链接:doc.rust-lang.org/nomicon/unwindin...
展开 (Unwinding)#
Rust 有一个分层的错误处理体系:
- 如果有些值可以为空,就用
Option
- 如果发生了错误,而错误可以被正常处理,就用
Result
- 如果发生了错误,但是没办法正常处理,就让线程 panic
- 如果发生了更严重的问题,中止 (abort) 程序
Option
和 Result
在大多数情况下都是默认的优先选择,因为 API 的用户可以根据自己的考虑将它们变为 panic 或中止。panic 会导致线程停止正常的执行流程、展开栈(unwind stack)、调用析构函数,整个流程和函数返回时一样。
从 1.0 开始,Rust 对 Panic 的处理显得有些混乱。在很早很早以前,Rust 的设计非常接近 Erlang。和 Erlang 一样,Rust 由许多轻量级的任务 (task) 组成,当任务进入错误状态的时候,它们使用 Panic 停止自己。Panic 和 Java 或者 C++ 中的异常不同,它不能在任意时间点被捕获。Panic 只能被任务的所有者捕获,而捕获后必须立即对它进行相应处理,否则任务会自己停止。
展开 (unwinding) 在这种场景下十分重要,因为如果任务的析构函数没有被调用的话,会导致内存和其他系统资源的泄露。由于任务有可能在正常运行过程中就挂掉,它对于需要长期运行的系统很不友好。
而在后来 Rust 的发展过程中,我们推崇尽可能少的抽象,所以上文的编程风格也就显得过时了。轻量级的任务被重量级的操作系统线程所取代。不过在 1.0 的稳定版本中,panic 还是只能被父线程捕获。这意味着捕获一个 panic 需要唤醒一个系统线程!这和 Rust 的零开销抽象的设计哲学是完全相悖的。
有一个不稳定的 API 叫做 catch_panic
,它可以在不启动一个线程的情况下捕获 panic。不过我们还是希望你谨慎地使用它。特别是现在 Rust 对展开的实现已经针对 “不展开” 的情况做了很多的优化。即使一个程序支持展开,只要它没有做展开的动作,在运行期就没有额外的开销。但同时,真的展开操作是比 Java 等其他语言的开销更大的。不要在正常运行的情况下让你的程序栈展开。只有当程序出错或遇到极端的问题时,你才应该使用 Panic。
Rust 的展开方式没有试图和其他任何一种语言的展开方式相兼容。所以,从其他语言展开 Rust 的栈,或者从 Rust 展开其他语言的栈,全都属于未定义行为。你必须在进入 FFI 调用之前捕获所有的 Panic!你可以决定具体的实现方法,但不能什么都不做。否则的话,最好的情况是你的应用程序会崩溃。而最坏的情况是,你的程序不会崩溃,但会在彻底混乱的状态下持续运行。