可恢复的错误Result
大多数错误没有严重到需要程序完全停止的程度。 有时,当函数失败时,这是由于您可以轻松解释的原因 并做出回应。例如,如果您尝试打开文件,但该作失败 由于该文件不存在,因此您可能希望创建该文件,而不是 终止进程。
召回自“处理潜在故障Result
”在第 2 章中,Result
enum 定义为具有两个
变种Ok
和Err
如下:
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
这T
和E
是泛型类型参数:我们将在 More
detail 在第 10 章中。您现在需要了解的是T
代表
在 Success case 中将返回的值的类型Ok
variant 和E
表示将在
failure 情况下的Err
变体。因为Result
具有以下泛型
参数,我们可以使用Result
type 及其上定义的函数
在许多不同的情况下,我们想要 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::open
function 需要有办法告诉我们它是成功还是失败,而在
同时给我们 File Handle 或 Error 信息。这
信息正是Result
enum conveys 的 intent 中。
在以下情况下File::open
succeeds,则变量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:使用match
expression 来处理Result
可能返回的变体
请注意,与Option
enum 中,Result
enum 及其变体已被
引入范围,因此我们不需要指定Result::
在Ok
和Err
变体match
武器。
当结果为Ok
,此代码将返回内部的file
值 out of
这Ok
variant 中,然后将该文件 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
返回值Err
variant 为io::Error
,这是标准库提供的 struct。这个结构体
具有kind
我们可以调用io::ErrorKind
价值。枚举io::ErrorKind
由标准库提供,并具有变体
表示io
操作。我们想要使用的变体是ErrorKind::NotFound
,它指示
我们尝试打开的文件尚不存在。所以我们匹配greeting_file_result
,但我们也有error.kind()
.
我们在内匹配中要检查的条件是,值是否返回
由error.kind()
是NotFound
变体ErrorKind
enum 中。如果是,
我们尝试使用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 具有许多帮助程序方法
定义以执行各种更具体的任务。这unwrap
method 是
shortcut 方法的实现方式与match
表达式
示例 9-4.如果Result
value 是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!
调用该unwrap
method 使:
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" }
同样,expect
method 让我们也选择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::open
succeeds,则 pattern 变量file
成为可变变量中的值username_file
和函数
继续。在Err
case 而不是调用panic!
,我们使用return
keyword 完全从函数中提前返回并传递 error 值
从File::open
,现在位于 pattern 变量e
,返回到调用代码
此函数的 error 值。
因此,如果我们在username_file
,该函数会创建一个
新增功能String
in 变量username
并调用read_to_string
method 开启
文件中的句柄username_file
将文件内容读入username
.这read_to_string
method 还会返回一个Result
因为它
可能会失败,即使File::open
成功。所以我们需要另一个match
自
处理那个Result
:如果read_to_string
succeeds,那么我们的函数就有
succeeded,然后我们从现在位于username
包装在Ok
.如果read_to_string
失败,则返回
与我们在match
处理了
返回值File::open
.但是,我们不需要明确地说return
,因为这是函数中的最后一个表达式。
然后,调用此代码的代码将处理获取Ok
价值
,其中包含 username 或Err
值,其中包含io::Error
.它
由调用代码决定如何处理这些值。如果调用
code 获取一个Err
value 中,它可以调用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:将错误返回给
调用代码?
算子
这?
放置在Result
value 的定义方式几乎相同
作为match
表达式来处理Result
列表中的值
9-6. 如果Result
是一个Ok
,则Ok
将
get 从此表达式返回,程序将继续。如果值
是一个Err
这Err
将从整个函数返回,就好像我们有
使用了return
关键字,以便将错误值传播到调用
法典。
与match
示例 9-6 中的 expression 执行
以及?
运算符执行:具有?
运算符调用
在他们身上,遍历from
函数,在From
trait 中的
标准库,用于将值从一种类型转换为另一种类型。
当?
运算符调用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::open
call 将
返回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>
因为可能存在
字符,但也有可能没有。此代码采用text
string 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
反之亦然;在这些情况下,
您可以使用诸如ok
method 开启Result
或ok_or
method 开启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
值要提前返回。即使
这main
function 只会返回std::io::Error
由
指定Box<dyn Error>
,则此签名将继续正确,即使
更多返回其他错误的代码被添加到main
.
当main
函数返回一个Result<(), E>
,可执行文件将退出并显示
值为0
如果main
返回Ok(())
,如果main
返回一个Err
价值。用 C 语言编写的可执行文件在以下情况下返回整数
they exit:成功退出的程序返回整数0
和程序
该错误返回除0
.Rust 还从
可执行文件以与此约定兼容。
这main
function 可以返回任何实现这std::process::Termination
特性,其中包含
函数report
返回一个ExitCode
.查阅标准库
documentation 有关实施Termination
的 trait
您自己的类型。
现在我们已经讨论了调用panic!
或返回Result
,
让我们回到如何决定哪个适合在哪个中使用
例。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准