自panic!
或不panic!
那么,您如何决定何时应该调用panic!
以及您应该何时返回Result
?当代码出现 panic 时,无法恢复。您可以调用panic!
对于任何错误情况,无论是否有可能的恢复方法,但是
那么你正在做出一个决定,即情况是无法恢复的
调用代码。当您选择返回Result
值,则为
调用代码选项。调用代码可以选择尝试在
方式,或者它可以决定Err
value 是不可恢复的,因此它可以调用panic!
并转动你的
可恢复的错误转换为不可恢复的错误。因此,返回Result
是一个
当您定义可能失败的函数时,这是 Good default 选择。
在示例、原型代码和测试等情况下,它更多
适合编写 panic 的代码,而不是返回Result
.让我们
探索原因,然后讨论编译器无法判断的情况
失败是不可能的,但你作为一个人可以。本章将以
有关如何决定是否在库代码中 panic 的一些通用准则。
示例、原型代码和测试
当您编写示例来说明某些概念时,还包括
健壮的错误处理代码可能会使示例不那么清晰。在示例中,它是
理解对unwrap
那 could panic 的意思是
placeholder 替换为您希望应用程序处理错误的方式,这可以
根据代码的其余部分执行的作而有所不同。
同样,unwrap
和expect
方法在原型设计时非常方便,
在您准备好决定如何处理错误之前。他们在
当您准备好使程序更加健壮时的代码。
如果方法调用在测试中失败,则您希望整个测试失败,即使
该方法不是待测试的功能。因为panic!
是测试
被标记为失败,调用unwrap
或expect
正是应该的
发生。
您拥有的信息比编译器多的情况
调用unwrap
或expect
当你有一些
其他逻辑,确保Result
将具有Ok
值,但 logic
不是编译器理解的。您仍然会有一个Result
价值
,您需要处理:无论您调用什么作,它仍然具有
一般情况下失败的可能性,即使这在逻辑上是不可能的
您的特定情况。如果可以通过手动检查代码来确保
你永远不会有Err
变体,则完全可以调用unwrap
,最好记录您认为您永远不会拥有Err
变体expect
发短信。下面是一个示例:
fn main() { use std::net::IpAddr; let home: IpAddr = "127.0.0.1" .parse() .expect("Hardcoded IP address should be valid"); }
我们正在创建一个IpAddr
instance 来解析硬编码字符串。我们可以看到
那127.0.0.1
是有效的 IP 地址,因此可以使用expect
这里。但是,具有硬编码的有效字符串不会更改返回类型
的parse
方法:我们仍然得到一个Result
值,编译器将
仍然让我们处理Result
就好像Err
variant 是一种可能性
因为编译器不够聪明,无法看到这个字符串始终是一个
有效的 IP 地址。如果 IP 地址字符串来自用户,而不是
硬编码到程序中,因此确实有失败的可能性,
我们肯定希望处理Result
相反,以更强大的方式。
提及此 IP 地址是硬编码的假设将提示我们
改变expect
以更好地处理代码,如果将来我们需要获取
来自其他来源的 IP 地址。
错误处理准则
建议在代码可能 panic 时让代码 panic 最终处于糟糕的状态。在这种情况下,不良状态是指当某些假设 保证、合约或不变量已被破坏,例如当无效值、 将矛盾值或缺失值传递给您的代码 - 加 1 或 更多以下内容:
- 不良状态是出乎意料的,而不是 可能偶尔会发生,例如用户在错误的 格式。
- 此之后的代码需要依赖于不处于这种 bad 状态, 而不是在每一步都检查问题。
- 没有一种将此信息编码为你使用的类型的好方法。我们将 通过一个示例来说明我们在 “编码状态和行为” 中的含义 as Types“部分。
如果有人调用您的代码并传入没有意义的值,则
如果可以,最好返回一个错误,以便库的用户可以决定什么
他们想在那种情况下这样做。但是,在继续可能
不安全或有害,最好的选择可能是调用panic!
并提醒
使用库的人来修复他们代码中的 bug,以便他们可以在
发展。同样地panic!
如果您调用
外部代码,并且它返回一个无效状态,该状态
你没有办法解决。
但是,当预期会失败时,返回Result
而不是使panic!
叫。示例包括给定格式错误的解析器
data 或 HTTP 请求返回指示您已达到某个速率的状态
限制。在这些情况下,返回Result
表示 failure 是
调用代码必须决定如何处理的预期可能性。
当您的代码执行可能使用户处于风险中的作时,如果它是
使用无效值调用,则代码应首先验证值是否有效
如果值无效,则 panic。这主要是出于安全原因:
尝试对无效数据进行作可能会使您的代码暴露于漏洞中。
这是标准库调用panic!
如果您尝试
越界内存访问:尝试访问不属于
当前数据结构是一个常见的安全问题。函数通常具有 contract:只有当 inputs 满足特定的
要求。在违反合同时惊慌是有道理的,因为
Contract Violation 始终表示调用方 bug ,它不是一种
错误。事实上,有
没有合理的方法来调用代码进行恢复;调用程序员需要
以修复代码。函数的协定,尤其是当违规将
导致 panic,应在函数的 API 文档中解释。
但是,在所有函数中都有大量错误检查会很冗长
而且很烦人。幸运的是,你可以使用 Rust 的类型系统(以及
checking done by the compiler)为你做很多检查。如果您的
function 具有特定类型作为参数,您可以继续执行代码的
logic 知道编译器已经确保您有一个有效的值。为
例如,如果你有一个 type 而不是Option
,您的程序期望
有总比什么都没有。然后,您的代码不必处理
两种情况Some
和None
variants:它只有一个 case
绝对有价值。尝试不向函数传递任何内容的代码不会
甚至编译,因此您的函数不必在运行时检查该情况。
另一个示例是使用无符号整数类型,例如u32
,这可确保
该参数永远不会为负数。
创建用于验证的自定义类型
让我们考虑使用 Rust 的类型系统来确保我们有一个有效的值 更进一步,看看如何创建自定义类型进行验证。回想一下 第 2 章中的猜谜游戏,其中我们的代码要求用户猜一个数字 介于 1 和 100 之间。我们从未验证过用户的猜测是否介于两者之间 号码,然后与我们的秘密号码进行核对;我们只验证了这一点 猜测是肯定的。在这种情况下,后果并不是很可怕:我们的 “Too high” 或 “Too Low” 的输出仍然是正确的。但这将是一个 有用的增强功能,可指导用户进行有效猜测,并具有不同的 当用户猜出超出范围的数字时的行为与 例如,用户键入 letters 来代替。
一种方法是将猜测解析为i32
而不仅仅是u32
以允许可能的负数,然后添加对
number 在 RANGE 中,如下所示:
文件名: src/main.rs
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
// --snip--
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
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--
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
这if
expression 检查我们的值是否超出范围,告诉用户
关于问题,并调用continue
开始循环的下一次迭代
并要求再次猜测。在if
表达式,我们可以继续执行
比较guess
和秘密号码知道guess
是
介于 1 和 100 之间。
但是,这并不是一个理想的解决方案:如果 程序仅对 1 到 100 之间的值进行作,并且它有很多函数 有了这个要求,在每个函数中都有这样的检查将是 繁琐的(并且可能会影响性能)。
相反,我们可以创建一个新类型并将验证放在一个函数中以创建
类型的实例,而不是到处重复验证。那
方式,函数可以安全地在其签名中使用新类型,并且
自信地使用他们收到的值。示例 9-13 展示了定义Guess
type 的 ID 实例,它只会创建Guess
如果new
功能
接收介于 1 和 100 之间的值。
文件名: src/lib.rs
#![allow(unused)] fn 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 } } }
示例 9-13:一个Guess
type 的
介于 1 和 100 之间的值
首先,我们定义一个名为Guess
,它有一个名为value
那
持有i32
.这是存储号码的地方。
然后我们实现一个名为new
上Guess
创建
的实例Guess
值。这new
function 被定义为具有一个
参数名称value
的类型i32
并返回一个Guess
.代码中的
body 的new
功能测试value
以确保它介于 1 和 100 之间。
如果value
未通过此测试,我们创建一个panic!
call 调用,它会提醒
编写调用代码的程序员,他们有他们需要的 bug
来修复,因为创建一个Guess
替换为value
超出此范围将
违反合同Guess::new
是依赖的。其中Guess::new
可能会恐慌 应该在其面向公众的 API 中讨论
文档;我们将介绍表明可能性的文档约定
的panic!
在第 14 章中创建的 API 文档中。如果value
通过测试,我们创建一个新的Guess
及其value
字段集
到value
参数并返回Guess
.
接下来,我们实现一个名为value
那个借用self
,没有任何
other 参数,并返回一个i32
.这种方法有时称为
一个 getter,因为它的目的是从其 fields 中获取一些数据并返回
它。此公共方法是必需的,因为value
字段的Guess
struct 是私有的。重要的是,value
字段为私有 so 代码
使用Guess
struct 不允许设置value
直接:外部代码
该模块必须使用Guess::new
函数创建Guess
,从而确保没有办法使用Guess
以具有value
那
未被Guess::new
功能。
具有参数或仅返回 1 到 100 之间的数字的函数可以
然后在其签名中声明它接受或返回一个Guess
而不是i32
并且不需要在其正文中执行任何其他检查。
总结
Rust 的错误处理功能旨在帮助您编写更健壮的代码。
这panic!
宏表示您的程序处于无法处理的状态,并且
用于指示进程停止,而不是尝试使用 Invalid 或
不正确的值。这Result
enum 使用 Rust 的类型系统来表示
作可能会以代码可以从中恢复的方式失败。您可以使用Result
告诉调用代码的代码它需要处理潜在的
成功或失败也是如此。用panic!
和Result
在适当的
情况会让你的代码在面对不可避免的问题时更加可靠。
现在,您已经了解了标准库使用 generics 的有用方法
这Option
和Result
enums,我们将讨论泛型的工作原理以及您
可以在您的代码中使用它们。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准