可恢复的错误Result
大多数错误没有严重到需要程序完全停止的程度。 有时,当函数失败时,这是由于您可以轻松解释的原因 并做出回应。例如,如果您尝试打开文件,但该作失败 由于该文件不存在,因此您可能希望创建该文件,而不是 终止进程。
召回自“处理潜在故障Result”在第 2 章中,Resultenum 定义为具有两个
变种Ok和Err如下:
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
这T和E是泛型类型参数:我们将在 More
detail 在第 10 章中。您现在需要了解的是T代表
在 Success case 中将返回的值的类型Okvariant 和E表示将在
failure 情况下的Err变体。因为Result具有以下泛型
参数,我们可以使用Resulttype 及其上定义的函数
在许多不同的情况下,我们想要 Success Value 和 Error 值
return 可能有所不同。
让我们调用一个函数,该函数返回一个Result值,因为该函数可以
失败。在示例 9-3 中,我们尝试打开一个文件。
文件名: src/main.rs
use std::fs::File; fn main() { let greeting_file_result = File::open("hello.txt"); }
示例 9-3:打开一个文件
返回类型File::open是一个Result<T, E>.泛型参数T已经被File::open使用
成功值,std::fs::File,它是一个文件句柄。的类型E用于
error 值为std::io::Error.此返回类型表示对File::open可能会成功并返回一个我们可以从 或
写入。函数调用也可能失败:例如,文件可能不会
存在,或者我们可能没有访问该文件的权限。这File::openfunction 需要有办法告诉我们它是成功还是失败,而在
同时给我们 File Handle 或 Error 信息。这
信息正是Resultenum conveys 的 intent 中。
在以下情况下File::opensucceeds,则变量greeting_file_result将是Ok,其中包含一个文件句柄。
在失败的情况下,中的greeting_file_result将是一个
实例Err,其中包含有关
发生。
我们需要添加到示例 9-3 中的代码中,以根据
在值File::open返回。示例 9-4 显示了一种处理Result使用基本工具match表达式,我们在
第 6 章.
文件名: src/main.rs
use std::fs::File; fn main() { let greeting_file_result = File::open("hello.txt"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => panic!("Problem opening the file: {error:?}"), }; }
示例 9-4:使用matchexpression 来处理Result可能返回的变体
请注意,与Optionenum 中,Resultenum 及其变体已被
引入范围,因此我们不需要指定Result::在Ok和Err变体match武器。
当结果为Ok,此代码将返回内部的file值 out of
这Okvariant 中,然后将该文件 handle 值分配给变量greeting_file.在match,我们可以使用文件句柄进行读取或
写作。
的另一只臂match处理我们获取Err值来自File::open.在此示例中,我们选择调用panic!宏。如果
当前目录中没有名为 hello.txt 的文件,我们运行此
code 中,我们将看到panic!宏:
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/error-handling`
thread 'main' panicked at src/main.rs:8:23:
Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
像往常一样,这个输出准确地告诉我们出了什么问题。
匹配不同的错误
示例 9-4 中的代码将panic!无论为什么File::open失败。
但是,我们希望针对不同的失败原因采取不同的作。如果File::open失败,因为文件不存在,我们想要创建该文件
并返回新文件的句柄。如果File::open任何其他失败
原因(例如,因为我们没有打开文件的权限),我们仍然
希望代码panic!就像示例 9-4 中所做的那样。为此,我们
添加内部match表达式,如示例 9-5 所示。
文件名: src/main.rs
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {e:?}"),
},
other_error => {
panic!("Problem opening the file: {other_error:?}");
}
},
};
}
示例 9-5:处理 中的不同类型的错误 不同的方式
该值的类型File::open返回值Errvariant 为io::Error,这是标准库提供的 struct。这个结构体
具有kind我们可以调用io::ErrorKind价值。枚举io::ErrorKind由标准库提供,并具有变体
表示io操作。我们想要使用的变体是ErrorKind::NotFound,它指示
我们尝试打开的文件尚不存在。所以我们匹配greeting_file_result,但我们也有error.kind().
我们在内匹配中要检查的条件是,值是否返回
由error.kind()是NotFound变体ErrorKindenum 中。如果是,
我们尝试使用File::create.但是,由于File::create也可能失败,我们需要在内部有第二个臂match表达。当
file 无法创建,则会打印不同的错误消息。的第二个臂
外部match保持不变,因此程序会因
缺少文件错误。
使用的替代方案match跟Result<T, E>
那可是很多match!这match表达式非常有用,但也非常
很原始。在第 13 章中,您将了解使用的闭包
在Result<T, E>.这些方法可以更多
比使用match处理时Result<T, E>值。
例如,这是另一种编写与 清单 中所示相同的逻辑的方法
9-5,这次使用闭包和unwrap_or_else方法:
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {error:?}");
})
} else {
panic!("Problem opening the file: {error:?}");
}
});
}
虽然这段代码的行为与示例 9-5 相同,但它不包含
任何match表达式,并且更易于阅读。回到这个例子
阅读完第 13 章后,查找unwrap_or_else方法中的
标准库文档。更多这些方法可以清理巨大的
嵌 套match表达式。
Error 时 panic 的快捷方式:unwrap和expect
用match效果很好,但可能有点冗长,而且并不总是如此
很好地传达意图。这Result<T, E>type 具有许多帮助程序方法
定义以执行各种更具体的任务。这unwrapmethod 是
shortcut 方法的实现方式与match表达式
示例 9-4.如果Resultvalue 是Ok变体unwrap将返回
值Ok.如果Result是Err变体unwrap将
调用panic!macro 的 Macro 来描述。下面是一个unwrap实际作:
文件名: src/main.rs
use std::fs::File; fn main() { let greeting_file = File::open("hello.txt").unwrap(); }
如果我们在没有 hello.txt 文件的情况下运行此代码,我们将看到来自
这panic!调用该unwrapmethod 使:
thread 'main' panicked at src/main.rs:4:49:
called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }
同样,expectmethod 让我们也选择panic!错误信息。
用expect而不是unwrap提供良好的错误消息可以传达
你的意图,并使追踪恐慌的来源更容易。的语法expect如下所示:
文件名: src/main.rs
use std::fs::File; fn main() { let greeting_file = File::open("hello.txt") .expect("hello.txt should be included in this project"); }
我们使用expect与unwrap:返回文件句柄或调用
这panic!宏。使用的错误消息expect在它对panic!将是我们传递给expect,而不是默认的panic!消息unwrap使用。这是它的样子:
thread 'main' panicked at src/main.rs:5:10:
hello.txt should be included in this project: Os { code: 2, kind: NotFound, message: "No such file or directory" }
在生产质量代码中,大多数 Rustacean 选择expect而不是unwrap并提供有关作为何应始终
成功。这样,如果你的假设被证明是错误的,你就会有更多的
调试中使用的信息。
传播错误
当函数的实现调用可能会失败的内容时,而不是 在函数本身内处理错误时,可以将错误返回到 调用代码,以便它可以决定要做什么。这称为传播错误,并为调用代码提供更多控制权,其中可能有更多 指示应如何处理错误的信息或逻辑,而不是 您在代码的上下文中可用。
例如,示例 9-6 展示了一个从文件中读取用户名的函数。如果 文件不存在或无法读取,此函数将返回这些错误 添加到调用该函数的代码中。
文件名: src/main.rs
#![allow(unused)] fn main() { use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let username_file_result = File::open("hello.txt"); let mut username_file = match username_file_result { Ok(file) => file, Err(e) => return Err(e), }; let mut username = String::new(); match username_file.read_to_string(&mut username) { Ok(_) => Ok(username), Err(e) => Err(e), } } }
示例 9-6:将错误返回给
使用match
这个函数可以用更短的方式编写,但我们要从
手动执行大量作以探索错误处理;最后,
我们将展示更短的方法。让我们看看函数的返回类型
第一:Result<String, io::Error>.这意味着该函数正在返回
value 类型的Result<T, E>,其中泛型参数T已经
填充 具体类型String和泛型类型E已经
填充 具体类型io::Error.
如果此函数成功且没有任何问题,则调用此
函数将接收一个Ok值,其中包含String- 该username那
此函数从文件中读取。如果此函数遇到任何问题,则
调用代码将收到一个Err值,其中包含io::Error其中包含有关问题所在的更多信息。我们选择了io::Error作为此函数的返回类型,因为那恰好是
type 我们从我们调用的两个作返回的错误值
此函数的主体可能会失败:File::open函数和read_to_string方法。
函数的主体首先调用File::open功能。然后我们
处理Result值替换为match类似于match在示例 9-4 中。
如果File::opensucceeds,则 pattern 变量file成为可变变量中的值username_file和函数
继续。在Errcase 而不是调用panic!,我们使用returnkeyword 完全从函数中提前返回并传递 error 值
从File::open,现在位于 pattern 变量e,返回到调用代码
此函数的 error 值。
因此,如果我们在username_file,该函数会创建一个
新增功能Stringin 变量username并调用read_to_stringmethod 开启
文件中的句柄username_file将文件内容读入username.这read_to_stringmethod 还会返回一个Result因为它
可能会失败,即使File::open成功。所以我们需要另一个match自
处理那个Result:如果read_to_stringsucceeds,那么我们的函数就有
succeeded,然后我们从现在位于username包装在Ok.如果read_to_string失败,则返回
与我们在match处理了
返回值File::open.但是,我们不需要明确地说return,因为这是函数中的最后一个表达式。
然后,调用此代码的代码将处理获取Ok价值
,其中包含 username 或Err值,其中包含io::Error.它
由调用代码决定如何处理这些值。如果调用
code 获取一个Errvalue 中,它可以调用panic!并崩溃程序,请使用
default username 的 NAME 中查找 USERNAME 的 ID 或 1 个应用程序
例。我们没有足够的信息来了解调用代码的实际含义
尝试执行作,因此我们将
它要适当地处理。
这种传播错误的模式在 Rust 中非常常见,以至于 Rust 提供了
问号运算符?以简化此作。
传播错误的快捷方式:?算子
示例 9-7 显示了read_username_from_file具有
功能与示例 9-6 中的相同,但此实现使用?算子。
文件名: src/main.rs
#![allow(unused)] fn main() { use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let mut username_file = File::open("hello.txt")?; let mut username = String::new(); username_file.read_to_string(&mut username)?; Ok(username) } }
示例 9-7:将错误返回给
调用代码?算子
这?放置在Resultvalue 的定义方式几乎相同
作为match表达式来处理Result列表中的值
9-6. 如果Result是一个Ok,则Ok将
get 从此表达式返回,程序将继续。如果值
是一个Err这Err将从整个函数返回,就好像我们有
使用了return关键字,以便将错误值传播到调用
法典。
与match示例 9-6 中的 expression 执行
以及?运算符执行:具有?运算符调用
在他们身上,遍历from函数,在Fromtrait 中的
标准库,用于将值从一种类型转换为另一种类型。
当?运算符调用from函数,则收到的错误类型为
转换为当前
功能。当函数返回一个错误类型来表示
函数可能失败的所有方式,即使部分可能会因许多不同的原因而失败
原因。
例如,我们可以更改read_username_from_file列表中的函数
9-7 返回一个名为OurError我们定义的。如果我们还
定义impl From<io::Error> for OurError要构造OurError从io::Error,则?operator 调用read_username_from_file将调用from并转换没有
需要向函数添加更多代码。
在示例 9-7 的上下文中,?在File::opencall 将
返回Ok添加到变量username_file.如果出现错误
发生时,该?operator 将在整个函数中 early 返回并给出
任何Err值添加到调用代码中。同样的事情也适用于?在
结束read_to_string叫。
这?operator 消除了大量的样板代码,并使该函数的
实现更简单。我们甚至可以通过链接
方法调用?,如示例 9-8 所示。
文件名: src/main.rs
#![allow(unused)] fn main() { use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let mut username = String::new(); File::open("hello.txt")?.read_to_string(&mut username)?; Ok(username) } }
示例 9-8:在?算子
我们已将新的String在username到
函数;这部分没有改变。而不是创建变量username_file,我们已将调用链接到read_to_string直接到
的结果File::open("hello.txt")?.我们仍然有一个?在read_to_string调用,我们仍然返回一个Ok值包含username当两者File::open和read_to_string成功而不是返回
错误。功能与示例 9-6 和示例 9-7 中的相同;
这只是一种不同的、更符合人体工程学的编写方式。
示例 9-9 展示了一种使用fs::read_to_string.
文件名: src/main.rs
#![allow(unused)] fn main() { use std::fs; use std::io; fn read_username_from_file() -> Result<String, io::Error> { fs::read_to_string("hello.txt") } }
示例 9-9:使用fs::read_to_string而不是
打开然后读取文件
将文件读入字符串是一个相当常见的作,因此标准的
库提供了方便的fs::read_to_string函数打开
file 中,创建一个新的String、读取文件的内容、放置内容
进入那个String,然后返回它。当然,使用fs::read_to_string没有给我们机会解释所有的错误处理,所以我们这样做了
长路先。
其中,?可以使用 Operator
这?operator 只能在返回类型兼容的函数中使用
值为?用于。这是因为?运算符
以相同的方式从函数中提前返回值
作为match表达式,我们在示例 9-6 中定义。在示例 9-6 中,match正在使用Result值,并且早期返回分支返回了一个Err(e)价值。函数的返回类型必须是Result因此
它与此兼容return.
在示例 9-10 中,让我们看看如果我们使用?算子
在main返回类型与
我们使用的价值?上。
文件名: src/main.rs
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")?;
}
示例 9-10:尝试使用?在main函数的 Json 函数不会编译。()
此代码将打开一个文件,这可能会失败。这?运算符遵循Result值返回者File::open,但是这个main函数的返回类型为 ,而不是()Result.当我们编译此代码时,我们收到以下错误
消息:
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:4:48
|
3 | fn main() {
| --------- this function should return `Result` or `Option` to accept `?`
4 | let greeting_file = File::open("hello.txt")?;
| ^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
help: consider adding return type
|
3 ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
4 | let greeting_file = File::open("hello.txt")?;
5 +
6 + Ok(())
7 + }
|
For more information about this error, try `rustc --explain E0277`.
error: could not compile `error-handling` (bin "error-handling") due to 1 previous error
这个错误指出我们只被允许使用?运算符在
函数返回Result,Option或其他实现FromResidual.
要修复此错误,您有两种选择。一种选择是更改返回类型
的函数中,以便与您正在使用的值兼容?算子
只要你没有限制阻止它。另一种选择是
使用match或其中一个Result<T, E>方法处理Result<T, E>以任何适当的方式。
错误消息还提到?可与Option<T>值
也。与使用?上Result,您只能使用?上Option在
函数返回一个Option.的行为?运算符
在Option<T>类似于它在Result<T, E>:
如果值为None这None将提前从
那个点。如果值为Some,则Some是
resultant 值,并且函数继续。示例 9-11 有
一个函数示例,该函数在
给定的文本。
fn last_char_of_first_line(text: &str) -> Option<char> { text.lines().next()?.chars().last() } fn main() { assert_eq!( last_char_of_first_line("Hello, world\nHow are you today?"), Some('d') ); assert_eq!(last_char_of_first_line(""), None); assert_eq!(last_char_of_first_line("\nhi"), None); }
示例 9-11:使用?运算符Option<T>价值
此函数返回Option<char>因为可能存在
字符,但也有可能没有。此代码采用textstring slice 参数并调用lines方法,该 API 将返回
字符串中各行的迭代器。因为这个函数想要
检查第一行,它会调用next获取第一个值
从迭代器。如果text是空字符串,则对next将
返回None,在这种情况下,我们使用?停止并返回None从last_char_of_first_line.如果text不是空字符串,next将
返回一个Some值,其中包含 中第一行的字符串切片text.
这?提取字符串 slice,我们可以调用chars在那个字符串切片上
获取其字符的迭代器。我们对 中的最后一个字符感兴趣
这第一行,所以我们调用last返回迭代器中的最后一项。
这是一个Option因为第一行可能是空的
字符串;例如,如果text以空行开头,但有 CHARACTERS ON
其他行,如"\nhi".但是,如果第一个字符上有最后一个字符
行中,它将在Some变体。这?运算符在中间
为我们提供了一种简洁的方式来表达这个逻辑,允许我们实现
函数。如果我们无法使用?运算符 onOption,我们会
必须使用更多方法调用或match表达。
请注意,您可以使用?运算符Result在返回Result,您可以使用?运算符Option在函数中,
返回Option,但您不能混合搭配。这?运算符不会
自动转换Result更改为Option反之亦然;在这些情况下,
您可以使用诸如okmethod 开启Result或ok_ormethod 开启Option以显式执行转换。
到目前为止,所有main我们使用的函数返回 .这()main函数为
special 的,因为它是可执行程序的入口点和出口点,
并且程序可以返回的 type 是有限制的
按预期作。
幸main也可以返回Result<(), E>.示例 9-12 的代码
从示例 9-10 开始,但我们更改了main成为Result<(), Box<dyn Error>>并添加了返回值Ok(())到最后。这
代码现在将编译。
文件名: src/main.rs
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
示例 9-12:更改main返回Result<(), E>允许使用?运算符 onResult值。
这Box<dyn Error>type 是一个 trait 对象,我们将在 “使用允许不同值的 trait 对象 ” 中讨论
Types“部分。现在,您可以
读Box<dyn Error>的意思是 “任何类型的错误”。用?在Result值在main错误类型为Box<dyn Error>允许
因为它允许任何Err值要提前返回。即使
这mainfunction 只会返回std::io::Error由
指定Box<dyn Error>,则此签名将继续正确,即使
更多返回其他错误的代码被添加到main.
当main函数返回一个Result<(), E>,可执行文件将退出并显示
值为0如果main返回Ok(()),如果main返回一个Err价值。用 C 语言编写的可执行文件在以下情况下返回整数
they exit:成功退出的程序返回整数0和程序
该错误返回除0.Rust 还从
可执行文件以与此约定兼容。
这mainfunction 可以返回任何实现这std::process::Termination特性,其中包含
函数report返回一个ExitCode.查阅标准库
documentation 有关实施Termination的 trait
您自己的类型。
现在我们已经讨论了调用panic!或返回Result,
让我们回到如何决定哪个适合在哪个中使用
例。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准