Rust 展开

Rust有一个分层的错误处理体系:

  • 如果有些值可以为空,就用Option
  • 如果发生了错误,而错误可以被正常处理,就用Result
  • 如果发生了错误,但是没办法正常处理,就让线程panic
  • 如果发生了更严重的问题,中止(abort)程序

OptionResult在大多数情况下都是默认的优先选择,因为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!你可以决定具体的实现方法,但不能什么都不做。否则的话,最好的情况是你的应用程序会崩溃。而最坏的情况是,你的程序不会崩溃,但会在彻底混乱的状态下持续运行。

下一章:Rust 异常安全性

虽然前面说过我们应该慎用展开,但是还是有许多的地方会Panic。如果你对None调用unwrap、使用超出范围的索引值、或者用0做除数,你的程序就要panic。在debug模式下,所有的计算操作在溢出的时候也都会pan ...