什么是panic
在Rust中,有一类错误叫作panic。示例如下:
编译,没有错误,执行这段程序,输出为:
这种情况就引发了一个panic。在这段代码中,我们调用了Option::unwrap()方法,正是这个方法有可能导致panic。根据提示,我们设置一个环境变量RUST_BACKTRACE=1之后再执行这个程序,可以看到这个程序在发生panic时候的函数调用栈。
Panic实现机制
在Rust中,Panic的实现机制有两种方式:unwind和abort。
- unwind方式在发生panic的时候,会一层一层地退出函数调用栈,在此过程中,当前栈内的局部变量还可以正常析构。
- abort方式在发生panic的时候,会直接退出整个程序。
在常见的操作系统上,默认情况下,编译器使用的是unwind方式。所以在发生panic的时候,我们可以通过一层层地调用栈找到发生panic的第一现场,就像前面例子展示的那样。
但是,unwind并不是在所有平台上都能获得良好支持的。在某些嵌入式系统上,unwind根本无法实现,或者占用的资源太多。在这种时候,我们可以选择使用abort方式实现panic.
编译器提供了一个选项,供用户指定panic的实现方式。
Panic Safety
C++中引入了“异常”这个机制之后,同时也带入了一个“异常安全”(exception safety)的概念。
异常安全存在四种层次的保证:
- No-throw——这种层次的安全性保证了所有的异常都在内部正确处理完毕,外部毫无影响;
- Strong exception safety——强异常安全保证可以保证异常发生的时候,所有的状态都可以“回滚”到初始状态,不会导致状态不一致的问题;
- Basic exception safety——基本异常安全保证可以保证异常发生的时候不会导致资源泄漏;
- No exception safety——没有任何异常安全保证。
在Rust中,什么情况下panic会导致bug呢?这种情况的产生需要两个条件:
- panic导致了数据结构内部的状态错误;
- 这个错误的状态会在以后被观测到。
在unsafe代码中,这种情况非常容易出现。所以,在写unsafe代码的时候,需要对这种情况非常敏感小心,一不小心就可能因为这个原因制造出“内存不安全”。
在不用unsafe的情况下,Panic Safety是基本有保障的。