Rust 语言学习笔记(四)
试手了一下 Rust, 发现止今所学知识尚浅,不少情况处理起来很是茫然,比如经常得到的值是 Result 或 Option, 有时候要 .unwrap(), 有时得后面加个问号,或 .await。还有从自定义函数里返回一个引用都不容易,到处是 move, borrow, owner, lifetime 之类的错误信息。所以说,快充五分钟还真不敢上路,继续学习 Rust 的 Result, 说起它又必须与 Rust 的错误处理联系起来。
编程语言在处理错误无外乎就两种哲学
第二种方式,在 shell 或 C++ 很常见,比如 shell 下每调用一个命令都有返回数值, 即 $?, 0 为正常,非 0 为有错误; C++ 的 GetLastError() 也是一样的意思。
现在所学的 Rust 在处理结果和错误时,采用了第二种方式,不过使用上 Rust 的模式匹配处理起 Option<T>, Result<T, E> 倒也不难,而且 Option<T> 和 Result<T, E> 这样的返回结果还用来向上传播异常,出错时还能追踪到异常栈。
Rust 提供了分层式错误处理的几种方案
Result<T, E> 是 Rust 内置并被自动引入的枚举
下面自定义一个方法返回 Result<T, E>, 然后看几个典型的应用
如果对 Err("overflow") 进行 .unwrap() 操作,会得到错误(Panic)
当然对 Ok(33) 也不能 unwrap_err() 操作, 也会有 Panic
某些时候
在 Result<T, E> 后加个
就因为 main() 不是返回 Result 或 Option。
因为 foo 函数返回的是 Result<T, E> 所以其中的 add_one(x)? 不再抱怨了。
再仔细看上面错误,好像调用函数返回 Result 或 Option 就能在其中用
编译又不过
所以前面说的 Result or Option 是骗人的,准确的说应该是函数中用
Option
foo 函数的等效代码如下
Result<T, E>
foo 函数的等效代码如下:
简单讲
ChatGPT 说推荐用
如果把
有种似曾相识的感觉,对就是 Groovy 的 Safe Navigation operator。
前面正好又从 Result<T, E> 没到了 Option<T>, 那就看看 Option<T> 应如何处理
Option<T> 也是一个枚举
包括两个值,有还无。Option 也有类似的 unwrap(), or(), or_else(), 通过 ok_or() 和 ok_or_else() 方法又可以把 Option 转换成一个 Result<T, E> 类型。
还有就是把 Option 用 match 或 if let 的模式匹配, 如 if let Some(x) = foo() { ...process x... }
std::thread::Result 是一个自定义的 Result
程序虽然能正常恢复,但在控制台下执行的是
如果 foo(0) 换成 foo(10), 执行后控制台输出为
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
编程语言在处理错误无外乎就两种哲学
- 得到正确的结果或触发异常,异常不就地处理则向外传播
- 总是能得到一个结果,于结果中获知是否有异常,如 Option<T>, Result<T, E>
第二种方式,在 shell 或 C++ 很常见,比如 shell 下每调用一个命令都有返回数值, 即 $?, 0 为正常,非 0 为有错误; C++ 的 GetLastError() 也是一样的意思。
现在所学的 Rust 在处理结果和错误时,采用了第二种方式,不过使用上 Rust 的模式匹配处理起 Option<T>, Result<T, E> 倒也不难,而且 Option<T> 和 Result<T, E> 这样的返回结果还用来向上传播异常,出错时还能追踪到异常栈。
Rust 提供了分层式错误处理的几种方案
- Option<T>: 有无值
- Result<T, E>: 有无错误
- Panic: 错误不可恢复,Go 语言也是用这个 panic 关键字
- Abort: 灾难性错误,一般都要终止进程了
Result<T, E> 是 Rust 内置并被自动引入的枚举
1enum Result<T, E> {
2 Ok(T),
3 Err(E),
4}下面自定义一个方法返回 Result<T, E>, 然后看几个典型的应用
1fn add_one(x: i32) -> Result<i32, &'static str> {
2 if x == i32::MAX {
3 Err("overflow")
4 } else {
5 Ok(x + 1)
6 }
7}
8
9fn main() {
10 let v = add_one(32).unwrap();
11 println!("v: {}", v); // v: 33
12
13 let e = add_one(i32::MAX).unwrap_err();
14 println!("e: {}", e); // e: overflow
15
16 if let Ok(ok_value) = add_one(32) {
17 println!("ok_value: {}", ok_value) // ok_value: 33
18 }
19
20 if let Err(err_str) = add_one(i32::MAX) {
21 println!("err_str: {}", err_str) // err_str: overflow
22 }
23}如果对 Err("overflow") 进行 .unwrap() 操作,会得到错误(Panic)
called `Result::unwrap()` on an `Err` value: "overflow"并在 Debug 模式下输出整个异常栈
当然对 Ok(33) 也不能 unwrap_err() 操作, 也会有 Panic
called `Result::unwrap_err()` on an `Ok` value: 33但可以用 Result<T, E> 的其他方法
- unwrap_or(default): 有错误则得到一个默认值
- expect("custom error"): 有错误仍然是一个 Panic, 可进一步自定义错误信息,如 Err("overflow").expect("custom error") 会看到 custom error: "overflow"。有一点像 PHP foo() or die("死给你看") 的意思
- expect_err(msg): 用到 Err 身上的方法,如果不是错误可不成
- is_ok(), is_error() 的判断
- ok(), error() 分别针对 Ok, Err 得到相应的 Option<T> , Option<E> 结果
- 其他更多如 unwrap_or_else(op), unwrap_or_default(default) 等
? 与 unwrap()
某些时候 ? 是 unwrap() 的快捷操作,但不是任何地方都适用。我们先在 main() 函中试下1fn main() {
2 let x = add_one(32)?; // 类型自动推断 x: i32
3 println!("{}", x);
4}在 Result<T, E> 后加个
? 号确实能让 x 类型在 IntelliJ IDEA 推断为 i32, 但无法通过编译,错误是 1error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
2 --> src/main.rs:12:24
3 |
49 | fn main() {
5 | --------- this function should return `Result` or `Option` to accept `?`
6...
712 | let x = add_one(32)?;
8 | ^ cannot use the `?` operator in a function that returns `()`
9 |
10 = help: the trait `FromResidual<Result<Infallible, &str>>` is not implemented for `()`就因为 main() 不是返回 Result 或 Option。
? 的操作是用来无法完成 unwrap() 时继续向外传播相应的 Result 或 Option, 所以创建一个返回 Result<T, E> 的函数来用 ? 就没问题1fn foo(x: i32) -> Result<i32, &'static str> {
2 let v: i32 = add_one(x)?; // 正常
3 Ok(v / 2)
4}
5
6fn main() {
7 let x: i32 = foo(i32::MAX).unwrap();
8 println!("{}", x);
9}因为 foo 函数返回的是 Result<T, E> 所以其中的 add_one(x)? 不再抱怨了。
再仔细看上面错误,好像调用函数返回 Result 或 Option 就能在其中用
? 代替 unwrap(), 那把 foo 函数改成返回 Option1fn foo(x: i32) -> Option<i32> {
2 let v: i32 = add_one(x)?;
3 Some(v / 2)
4}编译又不过
1error[E0277]: the `?` operator can only be used on `Option`s, not `Result`s, in a function that returns `Option`
2 --> src/main.rs:10:28
3 |
49 | fn foo(x: i32) -> Option<i32> {
5 | ----------------------------- this function returns an `Option`
610 | let v: i32 = add_one(x)?;
7 | ^ use `.ok()?` if you want to discard the `Result<Infallible, &str>` error information所以前面说的 Result or Option 是骗人的,准确的说应该是函数中用
? 代替 .unwrap() 的话,必须保证当前函数也是返回一样的类型,Result 对 Result, Option 对 Option。 因为 ? 代替 .unwrap() 的语义是,如果能 unwrap() 则往下处理,否则把 Result::Err 或 Option:None 的值作当前函数的返回值,也就要求 Err 中的错误类型要匹配。具体来说是Option
1fn foo(x: i32) -> Option<f64> {
2 let v: i32 = bar(x)?; // 用 bar(x)? 的话,foo 方法返回任意类型的 Option 都行
3 ...
4}
5
6fn bar(x: i32) -> Option<i32> {
7 ...
8}foo 函数的等效代码如下
1fn foo(x: i32) -> Option<f64> {
2 let v = bar(x);
3 if v == None {
4 return None;
5 }
6 ...
7}Result<T, E>
1fn foo(x: i32) -> Result<f64, &'static str> {
2 let v: i32 = bar(x)?; // 用 bar(x)? 的话,foo 方法只需保证返回的 Result 的 错误部类类型一致,此处都必须是 &'static str
3 ...
4}
5
6fn bar(x: i32) -> Result<i32, &'static str> {
7 ...
8}foo 函数的等效代码如下:
1fn foo(x: i32) -> Result<f64, &'static str> {
2 let v = bar(x);
3 if v.is_err() {
4 return Err(v.unwrap_err());
5 }
6 ...
7}简单讲
? 替换 .unwrap() 的作用是能在出错时提前返回 Result::Err(err) 或 Option::None, 这样应该更好理解调用函数需返回的类型。ChatGPT 说推荐用
?, 这样能更优雅的向上传播异常,除非确定 Result/Option 中有值就用 unwrap()。测试了一下,确实,在 foo() 函数中用了 add_on(x)? 异常栈里就能看到 foo() 函数调用,用 add_on(x).unwrap() 则差一些。如果把
? 号串起来,写成1let x = foo()?.bar()?.qux()?;有种似曾相识的感觉,对就是 Groovy 的 Safe Navigation operator。
前面正好又从 Result<T, E> 没到了 Option<T>, 那就看看 Option<T> 应如何处理
Option<T> 也是一个枚举
1enum Option<T> {
2 None,
3 Some(T),
4}包括两个值,有还无。Option 也有类似的 unwrap(), or(), or_else(), 通过 ok_or() 和 ok_or_else() 方法又可以把 Option 转换成一个 Result<T, E> 类型。
还有就是把 Option 用 match 或 if let 的模式匹配, 如 if let Some(x) = foo() { ...process x... }
抛出和捕获异常
相信我们多数人比较习惯的异常的处理方式是 throw/try/catch/finally, Python 用的关键字是 raise/try/except/finally, Go 的关键字是 defer/panic/recover。回到这里的 Rust 用的是什么语法呢,panic/catch_unwind, Rust 没有传统语言的 finally 1use std::panic::catch_unwind;
2
3fn foo(x: i32) -> i32 {
4 if x == 0 {
5 panic!("can't process 0")
6 }
7 x + 10
8}
9
10fn main() {
11 let y = catch_unwind(|| { // y 的类型自动推断为 std::thread::Result<i32>
12 foo(0) // 如果在这个闭包体中返回不同的类型,y 也将被推断为相应的 std::thread::Result<X> 类型
13 });
14
15 println!("{:?}", y); // Err(Any { .. })
16}std::thread::Result 是一个自定义的 Result
pub type Result通过 catch_unwind() 又重新把 panic 转换成的 Result, 还是要用 unwrap() 之类的方法去判断成功或失败,或获得其中的值= crate::result::Result >;
程序虽然能正常恢复,但在控制台下执行的是
cargo build --release 生成的执行文件,仍然打印出信息1thread 'main' panicked at src/main.rs:13:9:
2can't process 0
3note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
4Err(Any { .. })如果 foo(0) 换成 foo(10), 执行后控制台输出为
Ok(20)catch_unwind() 的最终目的是把 panic 转换成 Result。 永久链接 https://yanbin.blog/rust-language-learning-4/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。