🎃个人专栏:
🐬 算法设计与分析:算法设计与分析_IT闫的博客-CSDN博客
🐳Java基础:Java基础_IT闫的博客-CSDN博客
🐋c语言:c语言_IT闫的博客-CSDN博客
🐟MySQL:数据结构_IT闫的博客-CSDN博客
🐠数据结构:数据结构_IT闫的博客-CSDN博客
💎C++:C++_IT闫的博客-CSDN博客
🥽C51单片机:C51单片机(STC89C516)_IT闫的博客-CSDN博客
💻基于HTML5的网页设计及应用:基于HTML5的网页设计及应用_IT闫的博客-CSDN博客
🥏python:python_IT闫的博客-CSDN博客
🐠离散数学:离散数学_IT闫的博客-CSDN博客
🥽Linux:Linux_Y小夜的博客-CSDN博客
🚝Rust:Rust_Y小夜的博客-CSDN博客
欢迎收看,希望对大家有用!
目录
🎯总体原则
那么,该如何决定何时应该
panic!
以及何时应该返回Result
呢?如果代码 panic,就没有恢复的可能。你可以选择对任何错误场景都调用panic!
,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回Result
值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为Err
是不可恢复的,所以他们也可能会调用panic!
并将可恢复的错误变成了不可恢复的错误。因此返回Result
是定义可能会失败的函数的一个好的默认选择。
🎯编写示例、原型代码、测试
当你编写一个示例来展示一些概念时,在拥有健壮的错误处理代码的同时也会使得例子不那么明确。例如,调用一个类似
unwrap
这样可能panic!
的方法可以被理解为一个你实际希望程序处理错误方式的占位符,它根据其余代码运行方式可能会各不相同。类似地,在我们准备好决定如何处理错误之前,
unwrap
和expect
方法在原型设计时非常方便。当我们准备好让程序更加健壮时,它们会在代码中留下清晰的标记。如果方法调用在测试中失败了,我们希望这个测试都失败,即便这个方法并不是需要测试的功能。因为
panic!
会将测试标记为失败,此时调用unwrap
或expect
是恰当的。
🎯有时候你比编译器掌握更多信息
当你有一些其他的逻辑来确保
Result
会是Ok
值时,调用unwrap
或者expect
也是合适的,虽然编译器无法理解这种逻辑。你仍然需要处理一个Result
值:即使在你的特定情况下逻辑上是不可能的,你所调用的任何操作仍然有可能失败。如果通过人工检查代码来确保永远也不会出现Err
值,那么调用unwrap
也是完全可以接受的,这里是一个例子:use std::net::IpAddr; let home: IpAddr = "127.0.0.1" .parse() .expect("Hardcoded IP address should be valid");
我们通过解析一个硬编码的字符来创建一个
IpAddr
实例。可以看出127.0.0.1
是一个有效的 IP 地址,所以这里使用expect
是可以接受的。然而,拥有一个硬编码的有效的字符串也不能改变parse
方法的返回值类型:它仍然是一个Result
值,而编译器仍然会要求我们处理这个Result
,好像还是有可能出现Err
成员那样。这是因为编译器还没有智能到可以识别出这个字符串总是一个有效的 IP 地址。如果 IP 地址字符串来源于用户而不是硬编码进程序中的话,那么就 确实 有失败的可能性,这时就绝对需要我们以一种更健壮的方式处理Result
了。提及这个 IP 地址是硬编码的假设会促使我们将来把expect
替换为更好的错误处理,我们应该从其它代码获取 IP 地址。
🎯错误处理的指导性建议
在当有可能会导致有害状态的情况下建议使用
panic!
—— 在这里,有害状态是指当一些假设、保证、协议或不可变性被打破的状态,例如无效的值、自相矛盾的值或者被传递了不存在的值 —— 外加如下几种情况:
- 有害状态是非预期的行为,与偶尔会发生的行为相对,比如用户输入了错误格式的数据。
- 在此之后代码的运行依赖于不处于这种有害状态,而不是在每一步都检查是否有问题。
- 没有可行的手段来将有害状态信息编码进所使用的类型中的情况。
✨场景建议
如果别人调用你的代码并传递了一个没有意义的值,尽最大可能返回一个错误,如此库的用户就可以决定在这种情况下该如何处理。然而在继续执行代码是不安全或有害的情况下,最好的选择可能是调用
panic!
并警告库的用户他们的代码中有 bug,这样他们就会在开发时进行修复。类似的,如果你正在调用不受你控制的外部代码,并且它返回了一个你无法修复的无效状态,那么
panic!
往往是合适的。如果失败是可预期的:Result
当你的代码对值进行操作,首先验证这些值:panic!
✨为验证创建自定义类型
让我们使用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型以进行验证。回忆一下第二章的猜猜看游戏,我们的代码要求用户猜测一个 1 到 100 之间的数字,在将其与秘密数字做比较之前我们从未验证用户的猜测是位于这两个数字之间的,我们只验证它是否为正。在这种情况下,其影响并不是很严重:“Too high” 或 “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。
一种实现方式是将猜测解析成
i32
而不仅仅是u32
,来默许输入负数,接着检查数字是否在范围内: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-- }
if
表达式检查了值是否超出范围,告诉用户出了什么问题,并调用continue
开始下一次循环,请求另一个猜测。if
表达式之后,就可以在知道guess
在 1 到 100 之间的情况下与秘密数字作比较了。然而,这并不是一个理想的解决方案:如果让程序仅仅处理 1 到 100 之间的值是一个绝对需要满足的要求,而且程序中的很多函数都有这样的要求,在每个函数中都有这样的检查将是非常冗余的(并可能潜在的影响性能)。
相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全地在函数签名中使用新类型并相信它们接收到的值。
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 } }
我们定义了一个包含
i32
类型字段value
的结构体Guess
。这里是储存猜测值的地方。