结果的
可恢复错误
大多数错误没有严重到需要程序完全停止的程度。 有时,当函数失败时,这是由于您可以轻松解释的原因 并做出回应。例如,如果您尝试打开文件,但该作失败 由于该文件不存在,因此您可能希望创建该文件,而不是 终止进程。
回想一下第 2 章的 “Handling Potential Failure with Result
” 中,枚举被定义为具有两个
variants 和 ,如下所示:Result
Ok
Err
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
和 是泛型类型参数:我们将在
detail 在第 10 章中。您现在需要知道的是,它代表
在变体中的成功案例中将返回的值的类型,并表示将在
failure 大小写。因为有这些泛型
parameters 中,我们可以使用 type 和
在许多不同的情况下,我们想要 Success Value 和 Error 值
return 可能有所不同。T
E
T
Ok
E
Err
Result
Result
让我们调用一个返回值的函数,因为该函数可以
失败。在示例 9-3 中,我们尝试打开一个文件。Result
文件名: src/main.rs
use std::fs::File; fn main() { let greeting_file_result = File::open("hello.txt"); }
的返回类型是 .泛型参数已由 implementation of 填充,其类型为
success 值,即文件句柄。中使用的类型
错误值为 。此返回类型意味着对 的调用可能会成功,并返回我们可以从 或
写入。函数调用也可能失败:例如,文件可能不会
存在,或者我们可能没有访问该文件的权限。该函数需要有一种方法来告诉我们它是成功还是失败,并且在
同时给我们 File Handle 或 Error 信息。这
information 正是 enum 所传达的。File::open
Result<T, E>
T
File::open
std::fs::File
E
std::io::Error
File::open
File::open
Result
如果成功,则变量中的值将是包含文件句柄的实例。
在失败的情况下,值 in 将为
实例中包含有关
发生。File::open
greeting_file_result
Ok
greeting_file_result
Err
我们需要添加到示例 9-3 中的代码中,以根据
的值返回。示例 9-4 展示了一种使用基本工具的方法,我们在
第 6 章.File::open
Result
match
文件名: 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:?}"), }; }
请注意,与枚举一样,枚举及其变体也是
引入范围,因此我们不需要在 ARMS 中的 AND 变体之前指定。Option
Result
Result::
Ok
Err
match
当结果为 时,此代码将返回
变体,然后将该文件 handle 值分配给变量 .在 之后,我们可以使用文件句柄读取或
写作。Ok
file
Ok
greeting_file
match
另一个分支处理我们从 获取值的情况。在此示例中,我们选择调用宏。如果
当前目录中没有名为 hello.txt 的文件,我们运行此
code 中,我们将看到宏的以下输出:match
Err
File::open
panic!
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 中的代码无论为什么失败。
但是,我们希望针对不同的失败原因采取不同的作。如果因为文件不存在而失败,我们想创建该文件
并返回新文件的句柄。如果任何其他
原因(例如,因为我们没有打开文件的权限),我们仍然
希望代码像示例 9-4 中那样。为此,我们
添加一个内部表达式,如示例 9-5 所示。panic!
File::open
File::open
File::open
panic!
match
文件名: 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:?}");
}
},
};
}
在 variant 中返回的值的类型是 ,它是标准库提供的 struct 。这个结构体
有一个方法,我们可以调用它来获取一个值。枚举由标准库提供,并具有变体
表示作可能导致的不同种类的错误。我们要使用的变体是 ,它表示
我们尝试打开的文件尚不存在。所以我们在 上匹配,但我们在 上也有一个内部匹配。File::open
Err
io::Error
kind
io::ErrorKind
io::ErrorKind
io
ErrorKind::NotFound
greeting_file_result
error.kind()
我们在内匹配中要检查的条件是,值是否返回
by 是枚举的变体。如果是,
我们尝试使用 .但是,由于也可能失败,因此我们需要在 inner 表达式中使用第二个分支。当
file 无法创建,则会打印不同的错误消息。的第二个臂
outer 保持不变,因此程序会对
缺少文件错误。error.kind()
NotFound
ErrorKind
File::create
File::create
match
match
将 match
与 Result<T, E>
一起使用的替代方法
好多啊!这个表达式非常有用,但也非常
很原始。在第 13 章中,您将了解使用的闭包
在 上定义了许多方法。这些方法可以更多
比在代码中处理值时使用更简洁。match
match
Result<T, E>
match
Result<T, E>
例如,这是另一种编写与 清单 中所示相同的逻辑的方法
9-5,此时使用 closure 和方法: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 相同,但它不包含
any 表达式,并且更易于阅读。回到这个例子
阅读完第 13 章后,在
标准库文档。更多这些方法可以清理巨大的
嵌套表达式。match
unwrap_or_else
match
Panic on Error 的快捷方式:unwrap
和 expect
使用效果很好,但它可能有点冗长,而且并不总是如此
很好地传达意图。该类型具有许多帮助程序方法
定义以执行各种更具体的任务。该方法是一个
shortcut 方法的实现方式,就像我们在
示例 9-4.如果值是 variant,将返回
值 内 .如果 是变体,则
为我们调用 Macro。下面是一个实际作的示例:match
Result<T, E>
unwrap
match
Result
Ok
unwrap
Ok
Result
Err
unwrap
panic!
unwrap
文件名: src/main.rs
use std::fs::File; fn main() { let greeting_file = File::open("hello.txt").unwrap(); }
如果我们在没有 hello.txt 文件的情况下运行此代码,我们将看到来自
该方法进行的调用:panic!
unwrap
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
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"); }
我们以与 : 相同的方式返回文件句柄或调用
宏。在其调用 to 中使用的错误消息 by 将是我们传递给 的参数,而不是 use 的默认消息。这是它的样子: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 选择 instead 而不是 and 给出了更多上下文来说明为什么作应该总是
成功。这样,如果你的假设被证明是错误的,你就会有更多的
调试中使用的信息。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), } } }
这个函数可以用更短的方式编写,但我们要从
手动执行大量作以探索错误处理;最后,
我们将展示更短的方法。让我们看看函数的返回类型
第一:。这意味着该函数正在返回
type 的值,其中泛型参数已
填充了具体类型,并且泛型类型已被
填充了具体类型 .Result<String, io::Error>
Result<T, E>
T
String
E
io::Error
如果此函数成功且没有任何问题,则调用此
函数将接收一个值,该值包含一个 — 该
此函数从文件中读取。如果此函数遇到任何问题,则
调用 Code 将接收一个值,该值包含一个实例,其中包含有关问题所在的更多信息。我们选择作为此函数的返回类型,因为它恰好是
type 我们从我们调用的两个作返回的错误值
此函数的主体可能会失败:函数和方法。Ok
String
username
Err
io::Error
io::Error
File::open
read_to_string
函数的主体从调用函数开始。然后我们
handle 值,类似于示例 9-4 中的。
如果成功,则 pattern 变量中的文件句柄将成为 mutable 变量中的值,函数
继续。在这种情况下,我们不是调用 ,而是使用关键字来完全提前返回函数并传递 error 值
from ,现在在 pattern 变量中,返回到调用代码
此函数的 error 值。File::open
Result
match
match
File::open
file
username_file
Err
panic!
return
File::open
e
因此,如果我们在 中有一个文件句柄,该函数就会创建一个
new 在变量中调用
文件句柄 in 将文件内容读入 .该方法还返回一个 ,因为它
即使成功,也可能失败。所以我们需要另一个
处理 : 如果成功,那么我们的函数就有
成功,然后我们从现在包装在 .如果失败,则返回
与我们在处理
返回值 。但是,我们不需要显式地说 ,因为这是函数中的最后一个表达式。username_file
String
username
read_to_string
username_file
username
read_to_string
Result
File::open
match
Result
read_to_string
username
Ok
read_to_string
match
File::open
return
然后,调用此代码的代码将处理获取值
,其中包含 username 或包含 .它
由调用代码决定如何处理这些值。如果调用
code 获取一个值,它可能会调用并崩溃程序,请使用
default username 的 NAME 中查找 USERNAME 的 ID 或 1 个应用程序
例。我们没有足够的信息来了解调用代码的实际含义
尝试执行作,因此我们将
它要适当地处理。Ok
Err
io::Error
Err
panic!
这种传播错误的模式在 Rust 中非常常见,以至于 Rust 提供了
问号运算符来简化此作。?
传播错误的快捷方式:?
算子
示例 9-7 显示了一个具有
功能与示例 9-6 中的相同,但此实现使用 operator。read_username_from_file
?
文件名: 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) } }
放置在值之后的 is defined 以几乎相同的方式工作
作为表达式来处理 清单 中的值
9-6. 如果 the 的值是 an ,则 will 中的值
get 从此表达式返回,程序将继续。如果值
是一个 ,它将从整个函数中返回,就好像我们有
使用了关键字,以便将错误值传播到调用
法典。?
Result
match
Result
Result
Ok
Ok
Err
Err
return
示例 9-6 中的表达式所做的事情是不同的
以及运算符的作用:运算符名为
遍历函数,该函数在
标准库,用于将值从一种类型转换为另一种类型。
当作员调用函数时,收到的错误类型为
转换为当前
功能。当函数返回一个错误类型来表示
函数可能失败的所有方式,即使部分可能会因许多不同的原因而失败
原因。match
?
?
from
From
?
from
例如,我们可以更改 清单 中的函数
9-7 返回一个名为我们定义的自定义错误类型。如果我们还
define 构造 的实例,则 body 中的运算符调用将调用并转换没有
需要向函数添加更多代码。read_username_from_file
OurError
impl From<io::Error> for OurError
OurError
io::Error
?
read_username_from_file
from
在示例 9-7 的上下文中,调用结束时的 the 将
将 an 中的值返回给变量 。如果出现错误
发生时,运算符会提前返回整个函数,并给出
任何值。同样的事情也适用于 at
通话结束。?
File::open
Ok
username_file
?
Err
?
read_to_string
作员消除了大量的样板,并使这个函数的
实现更简单。我们甚至可以通过链接
方法调用,如示例 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) } }
我们已将 new in 的创建移至
函数;这部分没有改变。我们没有创建变量,而是将 to 的调用直接链接到
的结果。我们在调用的末尾仍然有 a,并且我们仍然返回一个包含 when 和 succeed 的值,而不是返回
错误。功能与示例 9-6 和示例 9-7 中的相同;
这只是一种不同的、更符合人体工程学的编写方式。String
username
username_file
read_to_string
File::open("hello.txt")?
?
read_to_string
Ok
username
File::open
read_to_string
示例 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") } }
将文件读入字符串是一个相当常见的作,因此标准的
库提供了方便的函数,打开
file, create a new , 读取文件的内容, 放置内容
放入该 ,并返回它。当然,using 并没有给我们解释所有错误处理的机会,所以我们这样做了
长路先。fs::read_to_string
String
String
fs::read_to_string
其中 ?
可以使用 Operator
运算符只能在返回类型兼容的函数中使用
替换为 the is used on 的值。这是因为运算符是定义的
以相同的方式从函数中提前返回值
作为我们在示例 9-6 中定义的表达式。在示例 9-6 中,使用的是 value,而 early return arm 返回了一个值。函数的返回类型必须是 ,以便
它与这个兼容。?
?
?
match
match
Result
Err(e)
Result
return
在示例 9-10 中,让我们看看如果我们使用 operator
在返回类型与
我们使用的值。?
main
?
文件名: src/main.rs
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")?;
}
此代码将打开一个文件,这可能会失败。运算符遵循 返回的值,但此函数的返回类型为 ,而不是 。当我们编译此代码时,我们收到以下错误
消息:?
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
要修复此错误,您有两种选择。一种选择是更改返回类型
的函数,以与您使用的值兼容运算符
只要你没有限制阻止它。另一种选择是
使用 A 或其中一种方法以任何适当的方式处理 。?
match
Result<T, E>
Result<T, E>
错误消息还提到了可以与值一起使用
也。与使用 on 一样,您只能在
函数返回一个 .运算符在调用时的行为
on 类似于它在 :
如果值为 ,则 将提前从位于
那个点。如果值为 ,则 中的值为
resultant 值,并且函数继续。示例 9-11 有
一个函数示例,该函数在
给定的文本。?
Option<T>
?
Result
?
Option
Option
?
Option<T>
Result<T, E>
None
None
Some
Some
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); }
此函数返回是因为可能存在
字符,但也有可能没有。此代码采用字符串 slice 参数并对其调用方法,该方法返回
字符串中各行的迭代器。因为这个函数想要
检查第一行,它调用 Iterator 来获取第一个值
从迭代器。如果 是空字符串,则对 will
return ,在这种情况下,我们使用 stop 和 return from 。如果 不是空字符串,则
返回一个值,其中包含 中第一行的字符串 slice。Option<char>
text
lines
next
text
next
None
?
None
last_char_of_first_line
text
next
Some
text
提取字符串 slice,我们可以调用该字符串 slice
获取其字符的迭代器。我们对 中的最后一个字符感兴趣
这第一行,所以我们调用以返回迭代器中的最后一项。
这是因为第一行可能是空的
字符串;例如,if 以空行开头,但
其他行,如 .但是,如果第一个字符上有最后一个字符
行,它将在 variant 中返回。中间的运算符
为我们提供了一种简洁的方式来表达这个逻辑,允许我们实现
函数。如果我们不能在 上使用运算符 ,我们将
必须使用更多方法调用或表达式来实现此逻辑。?
chars
last
Option
text
"\nhi"
Some
?
?
Option
match
请注意,您可以在返回 的函数中的 上使用运算符,也可以在 in 函数上使用运算符,该函数
返回 ,但不能混合和匹配。作员不会
自动将 A 转换为 AN,反之亦然;在这些情况下,
您可以使用 Method on 或 Method On 等方法来显式执行转换。?
Result
Result
?
Option
Option
?
Result
Option
ok
Result
ok_or
Option
到目前为止,我们使用的所有函数都返回 .函数是
special 的,因为它是可执行程序的入口点和出口点,
并且程序可以返回的 type 是有限制的
按预期作。main
()
main
幸运的是,也可以返回一个 .示例 9-12 的代码
从示例 9-10 开始,但我们把 的返回类型改成了 be,并在末尾添加了一个返回值。这
代码现在将编译。main
Result<(), E>
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(())
}
类型是一个 trait 对象,我们将在 “使用允许不同值的 trait 对象 ” 中讨论
Types“部分。现在,您可以
读作 “任何类型的错误”。允许在具有 error 类型的函数中使用值
,因为它允许提前返回任何值。即使
此函数将仅返回 , 由
指定 ,即使
返回其他错误的更多代码将添加到 的正文中。Box<dyn Error>
Box<dyn Error>
?
Result
main
Box<dyn Error>
Err
main
std::io::Error
Box<dyn Error>
main
当函数返回 时,可执行文件将退出,并显示
值 if 返回,如果返回值,则以非零值退出。用 C 语言编写的可执行文件在以下情况下返回整数
它们 exit:成功退出的程序返回 integer ,而 programs
该错误返回除 以外的某个整数。Rust 还从
可执行文件以与此约定兼容。main
Result<(), E>
0
main
Ok(())
main
Err
0
0
该函数可以返回实现 std::p rocess::Termination
trait 的任何类型的 intent,其中包含
一个返回 .查阅标准库
documentation 中有关为
您自己的类型。main
report
ExitCode
Termination
现在我们已经讨论了 call 或 return 的细节,
让我们回到如何决定哪个适合在哪个中使用
例。panic!
Result
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准