展开
Rust 有一个分层错误处理方案
- 如果某个事物可能合理地不存在,则使用 Option。
- 如果发生错误并且可以合理地处理,则使用 Result。
- 如果发生错误并且无法合理地处理,则线程会 panic。
- 如果发生灾难性的事情,程序会中止。
在大多数情况下,Option 和 Result 是首选,尤其因为它们可以由 API 用户酌情提升为 panic 或 abort。Panic 会导致线程停止正常执行并展开其堆栈,就像每个函数立即返回一样调用析构函数。
从 1.0 版本开始,Rust 在处理 panic 时有两种想法。在很久很久以前,Rust 更像 Erlang。像 Erlang 一样,Rust 拥有轻量级任务,并且当任务达到无法维持的状态时,它们会被设计成使用 panic 自杀。与 Java 或 C++ 中的异常不同,panic 不能在任何时候被捕获。Panic 只能由任务的所有者捕获,此时他们必须处理 panic,否则该任务本身也会 panic。
展开对这个故事很重要,因为如果任务的析构函数没有被调用,就会导致内存和其他系统资源的泄漏。由于任务预计会在正常执行期间死亡,这将使 Rust 非常不适合长期运行的系统!
随着我们今天所知的 Rust 的出现,这种编程风格在减少抽象的推动下逐渐过时。轻量级任务被以重量级操作系统线程的名义杀死了。尽管如此,在 1.0 版本中,稳定的 Rust 中 panic 只能由父线程捕获。这意味着捕获 panic 需要启动整个操作系统线程!不幸的是,这与 Rust 的零成本抽象理念相冲突。
有一个名为 catch_unwind
的 API 可以让你在不产生线程的情况下捕获 panic。尽管如此,我们还是建议你谨慎使用。特别是,Rust 当前的展开实现针对“不展开”的情况进行了大量优化。如果程序不展开,那么程序准备展开应该没有运行时成本。因此,实际展开将比在例如 Java 中更昂贵。不要让你的程序在正常情况下展开。理想情况下,你应该只在编程错误或极端问题时才 panic。
Rust 的展开策略未被指定为与其他任何语言的展开策略根本兼容。因此,从另一种语言展开到 Rust 中,或从 Rust 展开到另一种语言是未定义行为。你必须在 FFI 边界捕获任何 panic!你在那一点做什么取决于你,但必须做一些事情。如果你不这样做,最好的情况是你的应用程序将崩溃并烧毁。最糟糕的情况是,你的应用程序不会崩溃并烧毁,而是会继续执行,但状态完全混乱。