
9.4 什么时候该使用panic!9.4.1 总体原则在第9.1章「不可恢复的错误以及panic!」中也讲了Rust中的错误类型有两种可恢复的和不可恢复的。调用panic!就相当于发生了一个不可恢复的错误。返回Result类型意味着错误得到了传播而且这类错误是可恢复的。当你认为自己可以代替调用你代码的调用者来决定某些情况是不可恢复的时候就可以写panic!。如果你写的函数返回的是Result就相当于你把错误的处理权交给了代码的调用者。调用者就可以根据实际情况来决定是否要恢复这个错误当然它也可以觉得这个错误是不可恢复的然后自己调用panic!。总而言之如果你定义的是一个可能失败的函数那么优先考虑返回Result如果你认为某种情形是肯定不可恢复的那就使用panic!。9.4.2panic!适用的场景编写示例代码、用于演示某些概念的时候可以使用panic!。在这类程序里面处理错误通常是使用unwrap这类可以制造恐慌的办法。在这里unwrap就相当于一个占位符然后针对不同功能的不同错误再分别编写代码进行个性化的处理。在编写原型代码时可以使用panic!。因为在编写这类代码时还没想好该怎么处理错误unwrap和expect方法在原型设计时非常方便因为它们能制造恐慌在代码中留下清晰的标记后续就可以根据这些记号来对这些错误进行更具体的处理。在编写测试代码时可以使用panic!。因为如果测试代码中的某个方法调用失败了那么整个测试就应该被认定为失败而失败状态正可以通过panic!来标记。9.4.3 你比编译器更了解情况有时候你可以确定一个函数调用返回的一定是Ok绝对不会出现恐慌这个时候就可以使用unwrap。但是由于返回类型是类似Result这样的类型编译器仍然认为它可能出错而你知道它一定不可能。看个例子use std::net::IpAddr; fn main(){ let home: IpAddr 127.0.0.1.parse().unwrap(); }这个例子使用了IpAddr这个枚举。在main中对字符串127.0.0.1进行解析。我们知道127.0.0.1是一个合理的IP地址返回值一定是Ok所以后面就可以使用unwrap它绝对不会出现恐慌。9.4.5 错误处理的指导性建议当你的代码最终可能处于损坏状态(Bad State)时通常最好使用panic!。损坏状态是指某些假设、保证、约定或不可变性被打破了。比如说一些非法的值、矛盾的值或是空缺的值被传入代码并且下列中的任意一条成立- 这种损坏状态是一个意外。- 在此之后的代码如果处于这种损坏状态就无法继续运行。- 使用的类型中没有一个好方法来将这些处于损坏状态的信息进行编码。还是看一下具体的场景- 传入了无意义的参数值panic!- 调用外部不可控代码返回非法状态你又无法进行修复panic!- 如果失败是可预期的比如把字符串解析为数字Result- 当你的代码对值进行操作时首先应该验证值的合法性如果不合法panic!这一点主要出于安全性的考虑因为在尝试基于某些非法的值去进行操作的时候就可能会暴露代码中的漏洞。这也是标准库会在代码尝试越界访问时报错的原因尝试访问不属于当前数据结构的内存是一个普遍的安全问题。而且函数通常是有某种约定的只有在输入数据满足某些特定条件时才能够正常运行而在约定被违反时就应该触发恐慌。因为破坏这些约定往往预示着调用者端产生了bug而因此产生的错误也不应该留给调用者来解决应该就地处理触发恐慌。9.4.6 为验证创建自定义类型以2.1. 猜数游戏 Pt.1为例有些代码不重要就跳过了fn main() { loop { // --snip-- let guess: i32 match guess.trim().parse() { Ok(num) num, Err(_) continue, }; if guess 1 || guess 100 { println!(The secret number will be between 1 and 100.); continue; } match guess.cmp(secret_number) { // --snip-- } }这里对原本的代码进行了一些修改- 把guess的类型从u32改为i32这样就能接收负数。- 对于用户的输入小于1或大于100的情况提醒用户神秘数字在1到100之间。如果字符串转整数解析失败就会触发continue进行下一次迭代如果数字的范围不在1到100之间还会触发continue进行下一次迭代。针对这个小程序可以把验证直接写在main里面。但如果是一个大项目每个函数都需要验证的话那在每个函数里都反复写一遍验证逻辑就相当麻烦了。针对这种情况可以创建一个新的类型把验证逻辑放到构造这个新类型实例的函数里。这样只有通过验证的值才能成功创建出实例后续就不需要担心所接收值的有效性。看下例子pub struct Guess { value: i32, } impl Guess { pub fn new(value: i32) - Guess { if value 1 || value 100 { panic!(Guess value must be between 1 and 100, got {value}.); } Guess { value } } pub fn value(self) - i32 { self.value } } fn main() { loop { // --snip-- let guess: i32 match guess.trim().parse() { Ok(num) num, Err(_) continue, }; let guess Guess::new(guess); match guess.value().cmp(secret_number) { // --snip-- } }new就是实例构造器。如果值不在1到100之间就会panic!如果没发生恐慌那就创建一个Guess实例并把value字段设为传入的值。还定义了一个方法叫value它会从这个struct里提取value字段的值并返回。下面的main函数里就可以删掉验证值是否在1到100之间的操作了转而使用Guess::new这个构造器来验证。如果要使用到guess的实际值比如说在match的时候就可以使用value这个方法来获取。