🎃个人专栏:
🐬 算法设计与分析:算法设计与分析_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博客
欢迎收看,希望对大家有用!
目录
🎯Ruslt枚举
大部分错误并没有严重到需要程序完全停止执行。有时候,一个函数失败,仅仅就是因为一个容易理解和响应的原因。例如,如果因为打开一个并不存在的文件而失败,此时我们可能想要创建这个文件,而不是终止进程。
enum Result<T, E> { Ok(T), Err(E), }
T
和E
是泛型类型参数;T
代表成功时返回的Ok
成员中的数据的类型,而E
代表失败时返回的Err
成员中的错误的类型。因为Result
有这些泛型类型参数,我们可以将Result
类型和标准库中为其定义的函数用于很多不同的场景,这些情况中需要返回的成功值和失败值可能会各不相同。让我们调用一个返回
Result
的函数,因为它可能会失败:use std::fs::File; fn main() { let greeting_file_result = File::open("hello.txt"); }
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), }; }
注意与
Option
枚举一样,Result
枚举和其成员也被导入到了 prelude 中,所以就不需要在match
分支中的Ok
和Err
之前指定Result::
。这里我们告诉 Rust 当结果是
Ok
时,返回Ok
成员中的file
值,然后将这个文件句柄赋值给变量greeting_file
。match
之后,我们可以利用这个文件句柄来进行读写。
match
的另一个分支处理从File::open
得到Err
值的情况。在这种情况下,我们选择调用panic!
宏。
🎯匹配不同的错误
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); } }, }; }
File::open
返回的Err
成员中的值类型io::Error
,它是一个标准库中提供的结构体。这个结构体有一个返回io::ErrorKind
值的kind
方法可供调用。io::ErrorKind
是一个标准库提供的枚举,它的成员对应io
操作可能导致的不同错误类型。我们感兴趣的成员是ErrorKind::NotFound
,它代表尝试打开的文件并不存在。这样,match
就匹配完greeting_file_result
了,不过对于error.kind()
还有一个内层match
。我们希望在内层
match
中检查的条件是error.kind()
的返回值是否为ErrorKind
的NotFound
成员。如果是,则尝试通过File::create
创建文件。然而因为File::create
也可能会失败,还需要增加一个内层match
语句。当文件不能被创建,会打印出一个不同的错误信息。外层match
的最后一个分支保持不变,这样对任何除了文件不存在的错误会使程序 panic。
🎯unwrap
match
能够胜任它的工作,不过它可能有点冗长并且不总是能很好的表明其意图。Result<T, E>
类型定义了很多辅助方法来处理各种情况。其中之一叫做unwrap
,它的实现就类似于示例的match
语句。如果Result
值是成员Ok
,unwrap
会返回Ok
中的值。如果Result
是成员Err
,unwrap
会为我们调用panic!
。use std::fs::File; fn main() { let greeting_file = File::open("hello.txt").unwrap(); }
如果
Result
值是成员Ok
,unwrap
会返回Ok
中的值。如果Result
是成员Err
,unwrap
会为我们调用panic!
。
🎯expect
使用
expect
而不是unwrap
并提供一个好的错误信息可以表明你的意图并更易于追踪 panic 的根源。expect
的语法看起来像这样: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
的参数,而不像unwrap
那样使用默认的panic!
信息。
🎯传播错误
当编写一个其实先会调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定该如何处理。这被称为 传播(propagating)错误,这样能更好的控制代码调用,因为比起你代码所拥有的上下文,调用者可能拥有更多信息或逻辑来决定应该如何处理错误。
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), } }
这个函数可以编写成更加简短的形式,不过我们以大量手动处理开始以便探索错误处理;在最后我们会展示更短的形式。让我们看看函数的返回值:
Result<String, io::Error>
。这意味着函数返回一个Result<T, E>
类型的值,其中泛型参数T
的具体类型是String
,而E
的具体类型是io::Error
。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含
String
的Ok
值 —— 函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个Err
值,它储存了一个包含更多这个问题相关信息的io::Error
实例。这里选择io::Error
作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:File::open
函数和read_to_string
方法。函数体以调用
File::open
函数开始。接着使用match
处理返回值Result
,类似示例 9-4,如果File::open
成功了,模式变量file
中的文件句柄就变成了可变变量username_file
中的值,接着函数继续执行。在Err
的情况下,我们没有调用panic!
,而是使用return
关键字提前结束整个函数,并将来自File::open
的错误值(现在在模式变量e
中)作为函数的错误值传回给调用者。所以,如果在
username_file
中有一个文件句柄,该函数随后会在变量username
中创建一个新的String
并调用文件句柄username_file
上的read_to_string
方法,以将文件的内容读入username
。read_to_string
方法也返回一个Result
,因为它可能会失败,哪怕是File::open
已经成功了。因此,我们需要另一个match
来处理这个Result
:如果read_to_string
执行成功,那么这个函数也就成功了,我们将从文件中读取的用户名返回,此时用户名位于被封装进Ok
的username
中。如果read_to_string
执行失败,则像之前处理File::open
的返回值的match
那样返回错误值。然而,我们无需显式调用return
语句,因为这是函数的最后一个表达式。调用这个函数的代码最终会得到一个包含用户名的
Ok
值,或者一个包含io::Error
的Err
值。我们无从得知调用者会如何处理这些值。例如,如果他们得到了一个Err
值,他们可能会选择panic!
并使程序崩溃、使用一个默认的用户名或者从文件之外的地方寻找用户名。我们没有足够的信息知晓调用者具体会如何尝试,所以将所有的成功或失败信息向上传播,让他们选择合适的处理方法。这种传播错误的模式在 Rust 是如此的常见,以至于 Rust 提供了
?
问号运算符来使其更易于处理。
🎯?运算符
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) }
result
值之后的?
被定义为与示例中定义的处理Result
值的match
表达式有着完全相同的工作方式。如果Result
的值是Ok
,这个表达式将会返回Ok
中的值而程序将继续执行。如果值是Err
,Err
将作为整个函数的返回值,就好像使用了return
关键字一样,这样错误值就被传播给了调用者。示例中的
match
表达式与?
运算符所做的有一点不同:?
运算符所使用的错误值被传递给了from
函数,它定义于标准库的From
trait 中,其用来将错误从一种类型转换为另一种类型。当?
运算符调用from
函数时,收到的错误类型被转换为由当前函数返回类型所指定的错误类型。这在当函数返回单个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。例如,我们可以将示例 9-7 中的
read_username_from_file
函数修改为返回一个自定义的OurError
错误类型。如果我们也定义了impl From<io::Error> for OurError
来从io::Error
构造一个OurError
实例,那么read_username_from_file
函数体中的?
运算符调用会调用from
并转换错误而无需在函数中增加任何额外的代码。在示例的上下文中,
File::open
调用结尾的?
会将Ok
中的值返回给变量username_file
。如果发生了错误,?
运算符会使整个函数提前返回并将任何Err
值返回给调用代码。同理也适用于read_to_string
调用结尾的?
。
🎯?与from函数
- Trait std::convert::From上的from函数:
—用于错误之间的转换
- 被?所应用的错误,会隐式地被from函数处理
- 当?调用from函数时:
—它所接收的错误类型会被转化为当前函数返回值类型所定义的错误类型
- 用于:针对不同的错误原因,返回同一种错误类型
—只要每个错误类型实现了转换为所返回的错误类型的from函数
🎯?运算符只能返回Result函数
?
运算符只能被用于返回值与?
作用的值相兼容的函数。因为?
运算符被定义为从函数中提早返回一个值,match
作用于一个Result
值,提早返回的分支返回了一个Err(e)
值。函数的返回值必须是Result
才能与这个return
相兼容。use std::fs::File; fn main() { let greeting_file = File::open("hello.txt")?; }
这段代码打开一个文件,这可能会失败。
?
运算符作用于File::open
返回的Result
值,不过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 `()` For more information about this error, try `rustc --explain E0277`. error: could not compile `error-handling` due to previous error
这个错误指出只能在返回
Result
或者其它实现了FromResidual
的类型的函数中使用?
运算符。
🎯?运算符与main函数
- main函数的返回类型为:()(注解:单元类型,相当于什么都没返回)
- main函数的返回值也可以是Result<T,E>
use std::error::Error; use std::fs::File; fn main() -> Result<(), Box<dyn Error>> { let greeting_file = File::open("hello.txt")?; Ok(()) }
- Box<dtn Error>是trait对象;
可以简单的理解为“任何可能的错误类型”。