展开

Rust 有一种分层的错误处理方案

  • 如果某个值可能不存在,则使用 Option。
  • 如果出现问题并且可以合理地处理,则使用 Result。
  • 如果出现问题并且无法合理地处理,则线程会发生恐慌。
  • 如果发生灾难性错误,程序将中止。

在大多数情况下,强烈建议使用 Option 和 Result,特别是因为它们可以根据 API 用户的需要升级为恐慌或中止。恐慌会导致线程停止正常执行并展开其堆栈,调用析构函数,就好像每个函数都立即返回一样。

从 1.0 版本开始,Rust 对恐慌的态度就一直是矛盾的。在很久很久以前,Rust 更像 Erlang。与 Erlang 一样,Rust 也有轻量级任务,并且这些任务旨在在达到无法维持的状态时通过恐慌来自杀。与 Java 或 C++ 中的异常不同,恐慌不能在任何时候被捕获。恐慌只能由任务的所有者捕获,此时必须处理它们,否则任务本身也会发生恐慌。

展开对于这个故事很重要,因为如果一个任务的析构函数没有被调用,就会导致内存和其他系统资源泄漏。由于任务预计会在正常执行期间终止,这将使 Rust 非常不适合长时间运行的系统!

随着我们今天所知的 Rust 的出现,这种编程风格在减少抽象的推动下逐渐过时。为了重量级的操作系统线程,轻量级任务被扼杀了。尽管如此,在 1.0 版本的稳定版 Rust 中,恐慌只能由父线程捕获。这意味着捕获恐慌需要启动一个完整的操作系统线程!不幸的是,这与 Rust 零成本抽象的理念相冲突。

有一个名为 catch_unwind 的 API,它可以在不生成线程的情况下捕获恐慌。不过,我们建议您谨慎使用此功能。特别是,Rust 当前的展开实现针对“不展开”的情况进行了大量优化。如果程序不展开,那么程序为准备展开所付出的运行时成本应该为零。因此,实际展开的成本将比例如 Java 中的成本更高。不要构建在正常情况下展开的程序。理想情况下,您应该只对编程错误或极端问题感到恐慌。

Rust 的展开策略并没有规定要从根本上兼容任何其他语言的展开。因此,从另一种语言展开到 Rust,或从 Rust 展开到另一种语言都是未定义行为。您必须绝对在 FFI 边界捕获任何恐慌!在那时您做什么取决于您,但必须做一些事情。如果您没有做到这一点,最好的情况下,您的应用程序会崩溃并烧毁。最坏的情况下,您的应用程序不会崩溃并烧毁,而是会以完全损坏的状态继续运行。