[易学易懂系列|rustlang语言|零基础|快速入门|(20)|错误处理]

释放双眼,带上耳机,听听看~!

[易学易懂系列|rustlang语言|零基础|快速入门|(20)|错误处理]

实用知识

错误处理

我们今天来讲讲Rust中的错误处理。

很多语言都有自己的错误处理方式,比如,java是异常处理机制。

Rust有自己独特的错误处理机制。

在Rust有两种错误: recoverable and unrecoverable errors.

翻译成中文就是:可恢复错误和不可恢复错误。

Rust分别用两种方式来处理这两种错误:

1.Result用来处理可恢复错误,这种方式一般不会直接退出当前程序。

2.宏panic!用来处理不可恢复错误,这种方式一般会直接退出当前程序

什么是宏?

宏是用来生成代码的,在调用宏的地方,编译器会先将宏进行展开,生成代码,然后再编译展开后的代码。

我们接下来的一篇文章会讲到,现在只要记住它是用来生成代码的,就行了。

回到正题。

我们先来看看panic!,先看看简单的代码:


1
2
3
4
1fn main() {
2    panic!("crash and burn");
3}
4

运行代码,会产生这样的结果:


1
2
3
4
5
6
7
1$ cargo run
2   Compiling panic v0.1.0 (file:///projects/panic)
3    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
4     Running `target/debug/panic`
5thread 'main' panicked at 'crash and burn', src/main.rs:2:5
6note: Run with `RUST_BACKTRACE=1` for a backtrace.
7

这里的信息,明确地指出panic发生的代码行。

我们再看看另一个std标准库的例子:


1
2
3
4
5
6
1fn main() {
2    let v = vec![1, 2, 3];
3
4    v[99];
5}
6

运行代码,我们又会产生这样的结果:


1
2
3
4
5
6
7
1$ cargo run
2   Compiling panic v0.1.0 (file:///projects/panic)
3    Finished dev [unoptimized + debuginfo] target(s) in 0.27s
4     Running `target/debug/panic`
5thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', libcore/slice/mod.rs:2448:10
6note: Run with `RUST_BACKTRACE=1` for a backtrace.
7

这段代码,直接访问超出vector下标的元素,系统直接报告一个不可恢复的错误,并直接退出程序。

我们再来看看Result。

先看看简单的例子:


1
2
3
4
5
6
1use std::fs::File;
2
3fn main() {
4    let f = File::open("hello.txt");
5}
6

运行代码,出现如下结果:


1
2
3
4
5
6
7
8
9
10
1error[E0308]: mismatched types
2 --> src/main.rs:4:18
3  |
44 |     let f: u32 = File::open("hello.txt");
5  |                  ^^^^^^^^^^^^^^^^^^^^^^^ expected u32, found enum
6`std::result::Result`
7  |
8  = note: expected type `u32`
9             found type `std::result::Result<std::fs::File, std::io::Error>`
10

这段错误信息告诉我们,我们的代码要有返回值 :Result<T, E>

那好,我们来修改代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
1use std::fs::File;
2
3fn main() {
4    let f = File::open(&quot;hello.txt&quot;);
5
6    let _f = match f {
7        Ok(file) =&gt; file,
8        Err(error) =&gt; {
9            panic!(&quot;Problem opening the file: {:?}&quot;, error)
10        },
11    };
12}
13

当然,我们这里也可以直接用另一种写法(Result的unwrap),这种 写法我们在下面会详细说明 :


1
2
3
4
5
6
1use std::fs::File;
2
3fn main() {
4    let f = File::open(&quot;hello.txt&quot;).unwrap();
5}
6

我们可以用模式匹配来处理,这种方法比java等语言的代码,简洁多了。漂亮!

我们还是从显式的模式匹配代码看看逻辑:


1
2
3
4
5
6
7
1 let _f = match f {
2        Ok(file) =&gt; file,
3        Err(error) =&gt; {
4            panic!(&quot;Problem opening the file: {:?}&quot;, error)
5        },
6    };
7

如果文件hello.txt在当前工程目录存在,匹配到:Ok,并返回文件句柄( file handle)。

如果文件不存在,就直接返回错误(panic!),并直接退出当前程序。

好,运行代码,得出结果:


1
2
3
4
1thread &#x27;main&#x27; panicked at &#x27;Problem opening the file: Os { code: 2, kind: NotFound, message: &quot;系统找不到指定的文件。&quot; }&#x27;, src\main.rs:123:23
2note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
3error: process didn&#x27;t exit successfully: `target\debug\hello.exe` (exit code: 101)
4

因为当前目录不存在文件hello.txt,所以程序直接返回panic错误。

好理解。

好,我们现在再改造一下代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1use std::fs::File;
2use std::io::ErrorKind;
3
4fn main() {
5    let f = File::open(&quot;hello.txt&quot;);
6
7    let _fh = match f {
8        Ok(file) =&gt; file,
9        Err(error) =&gt; match error.kind() {
10            ErrorKind::NotFound =&gt; match File::create(&quot;hello.txt&quot;) {
11                Ok(fc) =&gt; fc,
12                Err(e) =&gt; panic!(&quot;Problem creating the file: {:?}&quot;, e),
13            },
14            other_error =&gt; panic!(&quot;Problem opening the file: {:?}&quot;, other_error),
15        },
16    };
17}
18

我们再对不同的error再进行细粒度处理。

1.如果文件存在,就创建一个文件:hello.txt,那创建的过程中,又会出现 两种 情况 :

1.1创建成功,直接返回文件句柄。

1.2创建失败,直接调用panic!宏。

运行代码, 程序会自己在工程根目录创建一个空的文件:hello.txt。

我们看到上面 的代码,太多模式匹配,有点复杂,能不能再简洁一点?

可以的,请看下面代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1use std::fs::File;
2use std::io::ErrorKind;
3
4fn main() {
5    let f = File::open(&quot;hello.txt&quot;).unwrap_or_else(|error| {
6        if error.kind() == ErrorKind::NotFound {
7            File::create(&quot;hello.txt&quot;).unwrap_or_else(|error| {
8                panic!(&quot;Problem creating the file: {:?}&quot;, error);
9            })
10        } else {
11            panic!(&quot;Problem opening the file: {:?}&quot;, error);
12        }
13    });
14}
15

在Rust,我们用闭包让代码再简洁可读。完美!

其中unwrap_or_else方法,是Result这个枚举的方法。

它主要的用法是,打开Result,如果OK,就直接取OK中的内容,否则,

如果它是Err,则执行后面的闭包,请看基本用法的简单例子:


1
2
3
4
5
1fn count(x: &amp;str) -&gt; usize { x.len() }
2
3assert_eq!(Ok(2).unwrap_or_else(count), 2);
4assert_eq!(Err(&quot;foo&quot;).unwrap_or_else(count), 3);
5

好的。

回到我们的panic错误处理。

Rust有两种简洁写法:unwrap and expect

我们来一一分析下,先来看unwrap方法:


1
2
3
4
5
6
1use std::fs::File;
2
3fn main() {
4    let f = File::open(&quot;hello.txt&quot;).unwrap();
5}
6

运行代码,返回错误:


1
2
3
4
1thread &#x27;main&#x27; panicked at &#x27;called `Result::unwrap()` on an `Err` value: Error {
2repr: Os { code: 2, message: &quot;No such file or directory&quot; } }&#x27;,
3src/libcore/result.rs:906:4
4

这里错误信息都是默认信息: message: "No such file or directory"

能不能自定义呢?

可以的。

用expect,请看例子:


1
2
3
4
5
6
1use std::fs::File;
2
3fn main() {
4    let f = File::open(&quot;hello.txt&quot;).expect(&quot;Failed to open hello.txt&quot;);
5}
6

运行代码,返回错误:


1
2
3
1thread &#x27;main&#x27; panicked at &#x27;Failed to open hello.txt: Error { repr: Os { code:
22, message: &quot;No such file or directory&quot; } }&#x27;, src/libcore/result.rs:906:4
3

这里的错误信息,就我们自定义的:Failed to open hello.txt

很好!

用自定义的错误信息,可以让我们更好地定位问题。

错误信息的传播

我们再来看看这样的代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1use std::io;
2use std::io::Read;
3use std::fs::File;
4
5fn read_username_from_file() -&gt; Result&lt;String, io::Error&gt; {
6    let f = File::open(&quot;hello.txt&quot;);
7
8    let mut f = match f {
9        Ok(file) =&gt; file,
10        Err(e) =&gt; return Err(e),
11    };
12
13    let mut s = String::new();
14
15    match f.read_to_string(&amp;mut s) {
16        Ok(_) =&gt; Ok(s),
17        Err(e) =&gt; Err(e),
18    }
19}
20

这里我们定义一个方法,用来从文件中读username。

这里我们用了模式匹配代码,逻辑很清楚,但很复杂很繁琐。

有没有更好的写法?

有的。

在Rust,我们可以这样写:


1
2
3
4
5
6
7
8
9
10
11
1use std::io;
2use std::io::Read;
3use std::fs::File;
4
5fn read_username_from_file() -&gt; Result&lt;String, io::Error&gt; {
6    let mut f = File::open(&quot;hello.txt&quot;)?;
7    let mut s = String::new();
8    f.read_to_string(&amp;mut s)?;
9    Ok(s)
10}
11

所有模式匹配处理错误的代码,都可以用?替换,简洁很多!很好!

有没有更简洁的方式 ?

有的。

请看下面代码:


1
2
3
4
5
6
7
8
9
10
11
12
1use std::io;
2use std::io::Read;
3use std::fs::File;
4
5fn read_username_from_file() -&gt; Result&lt;String, io::Error&gt; {
6    let mut s = String::new();
7
8    File::open(&quot;hello.txt&quot;)?.read_to_string(&amp;mut s)?;
9
10    Ok(s)
11}
12

我们可以用链式方法。漂亮!

当然,更好的方式,是直接用标准库里的方法,那就更简洁了:


1
2
3
4
5
6
7
8
9
10
11
1use std::io;
2use std::fs;
3
4fn read_username_from_file() -&gt; Result&lt;String, io::Error&gt; {
5    fs::read_to_string(&quot;hello.txt&quot;)
6}
7fn main() {
8    let r = read_username_from_file();
9    println!(&quot;the string in hello.txt is {:?}&quot;, r);
10}
11

实际项目开发中,我们更推荐直接用标准库里的方法。因为标准库的方法,更少出错,经过更多测试,久经考验!

我们这里要特别强调一点就是:?只能用于有返回值并且返回值为Result<T,E>(或Option
)的方法。

不信?

我们来做做实验:


1
2
3
4
5
6
1use std::fs::File;
2
3fn main() {
4    let f = File::open(&quot;hello.txt&quot;)?;
5}
6

运行上面的代码,会报错误:


1
2
3
4
5
6
7
8
9
10
11
1error[E0277]: the `?` operator can only be used in a function that returns
2`Result` or `Option` (or another type that implements `std::ops::Try`)
3 --&gt; src/main.rs:4:13
4  |
54 |     let f = File::open(&quot;hello.txt&quot;)?;
6  |             ^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a
7  function that returns `()`
8  |
9  = help: the trait `std::ops::Try` is not implemented for `()`
10  = note: required by `std::ops::Try::from_error`
11

怎么办?

改造一下代码:


1
2
3
4
5
6
7
8
9
1use std::error::Error;
2use std::fs::File;
3
4fn main() -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
5    let f = File::open(&quot;hello.txt&quot;)?;
6
7    Ok(())
8}
9

这里的Box<

,是智能指针,它用来封装不同的错误类型。

现在main函数可以正常编译了。

以上,希望对你有用。


1
2
1如果遇到什么问题,欢迎加入:rust新手群,在这里我可以提供一些简单的帮助,加微信:360369487,注明:博客园+rust
2

参考文章:

https://doc.rust-lang.org/stable/book/ch09-02-recoverable-errors-with-result.html

https://learning-rust.github.io/docs/e2.panicking.html

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C++ explicit关键字

2022-1-11 12:36:11

安全运维

ElasticSearch大数据分布式弹性搜索引擎使用—从0到1

2021-12-11 11:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索