可恢复错误(Result<T,E>)
Result是Rust中用于处理错误的核心类型之一,封装了一个成功的值或者一个错误
Ok(T)
表示操作成功,并包含一个类型为T的值Err(E)
表示操作失败,并包含一个类型为E的错误
#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
#[rustc_diagnostic_item = "Result"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Result<T, E> {
/// Contains the success value
#[lang = "Ok"]
#[stable(feature = "rust1", since = "1.0.0")]
Ok(#[stable(feature = "rust1", since = "1.0.0")] T),
/// Contains the error value
#[lang = "Err"]
#[stable(feature = "rust1", since = "1.0.0")]
Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}
尝试打开一个文件
fn main() {
let file_result = File::open("hello.txt");
match file_result {
Ok(file) => { println!("{:?}", file); }
Err(err) => { println!("err: {:?}", err) }
}
}
如果这个文件不存在
➜ hello_rust git:(master) ✗ cargo run
Compiling hello_rust v0.1.0 (/Users/kl/rust/hello_rust)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.70s
Running `target/debug/hello_rust`
err: Os { code: 2, kind: NotFound, message: "No such file or directory" }
➜ hello_rust git:(master) ✗
如果创建了这个文件
➜ hello_rust git:(master) ✗ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/hello_rust`
File { fd: 3, path: "/Users/kl/rust/hello_rust/hello.txt", read: true, write: false }
➜ hello_rust git:(master) ✗
不可恢复错误(panic)
fn main() {
happen_panic()
}
fn happen_panic(){
panic!("index out of bound...")
}
直接运行:cargo run
➜ hello_rust git:(master) ✗ cargo run
warning: `hello_rust` (bin "hello_rust") generated 7 warnings (run `cargo fix --bin "hello_rust"` to apply 3 suggestions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/hello_rust`
thread 'main' panicked at src/main.rs:33:5:
index out of bound...
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
➜ hello_rust git:(master) ✗
运行并打印堆栈:RUST_BACKTRACE=1 cargo run
- 可以看到
stack backtrace
还标注了序号,序号3调用了序号2发生的panic,所以2的栈在3的顶上,堆栈中从上往下当前序号的上一位是自己调用的方法,下一位是调用自己的方法 - RUST_BACKTRACE不用特别指定为1,任意数字都行
➜ hello_rust git:(master) ✗ RUST_BACKTRACE=1 cargo run
warning: `hello_rust` (bin "hello_rust") generated 7 warnings (run `cargo fix --bin "hello_rust"` to apply 3 suggestions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
Running `target/debug/hello_rust`
thread 'main' panicked at src/main.rs:33:5:
index out of bound...
stack backtrace:
0: rust_begin_unwind
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/panicking.rs:662:5
1: core::panicking::panic_fmt
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/core/src/panicking.rs:74:14
2: hello_rust::happen_panic
at ./src/main.rs:33:5
3: hello_rust::main
at ./src/main.rs:29:5
4: core::ops::function::FnOnce::call_once
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
➜ hello_rust git:(master) ✗
错误传播
函数报错之后抛出异常
基础方式-match抛出
fn main() {
let res = read_file();
match res {
Ok(str) => { println!("{}", str) }
Err(err) => { println!("{:?}", err) }
}
}
fn read_file() -> Result<String, io::Error> {
let file_result = File::open("hello.txt");
// 如果加分号,match必须赋值给某个变量,该变量类型为Ok中返回的值类型
let mut f = match file_result {
Ok(file) => file, // 赋值给变量f
Err(err) => return Err(err), // 因为返回值是Result,所以这里可以直接return
};
let mut s = String::new();
// 如果不加分号,match作为一个表达式返回Result
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(err) => Err(err)
}
}
通过?传播
- 如果有错误就直接返回错误
- 如果没有错误返回真实的值,并继续往下执行
use std::fs::File;
use std::io;
use std::io::{Error, Read};
fn main() {
let res = read_file();
match res {
Ok(str) => { println!("{}", str) }
Err(err) => { println!("{:?}", err) }
}
}
fn read_file() -> Result<String, io::Error> {
let mut file = File::open("hello.txt")?;
let mut s = String::new();
// 如果不加分号,match作为一个表达式返回Result
file.read_to_string(&mut s)?;
Ok(s)
}
最简版
fn read_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
如果出现错误,输出:
Os { code: 2, kind: NotFound, message: "No such file or directory" }
进程已结束,退出代码为 0
附加context上下文可以有效的帮助开发人员排查问题到底出在哪里。如果没有更多的上下文信息,返回像“No such file or directory”这样的低级错误信息就很烦人,因为看不出来具体到底是哪个文件或者目录不存在。而anyhow
可以帮助我们解决这个问题。
anyhow
anyhow
是一个常用的错误处理库,提供了简单和灵活的错误处理方式。它的设计目标是简化复杂的错误处理,适用于需要在应用程序中方便地捕获和处理各种错误的场景。
[dependencies]
anyhow = "1.0"
示例
use anyhow::Error as AnyhowError;
fn main() {
let res = read_file();
match res {
Ok(str) => { println!("{}", str) }
Err(err) => { println!("{:?}", err) }
}
}
fn read_file() -> Result<String, AnyhowError> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
输出:可以通过堆栈看出是./src/main.rs:299:5
代码错误
No such file or directory (os error 2)
Stack backtrace:
0: std::backtrace_rs::backtrace::libunwind::trace
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/../../backtrace/src/backtrace/libunwind.rs:116:5
1: std::backtrace_rs::backtrace::trace_unsynchronized
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
2: std::backtrace::Backtrace::create
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/backtrace.rs:331:13
3: anyhow::error::<impl core::convert::From<E> for anyhow::Error>::from
at /Users/liukunlong/.cargo/registry/src/rsproxy.cn-0dccff568467c15b/anyhow-1.0.89/src/backtrace.rs:27:14
4: <core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/core/src/result.rs:1989:27
5: hello_rust::read_file
at ./src/main.rs:299:5
6: hello_rust::main
at ./src/main.rs:290:15
7: core::ops::function::FnOnce::call_once
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/core/src/ops/function.rs:250:5
8: std::sys::backtrace::__rust_begin_short_backtrace
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/sys/backtrace.rs:154:18
9: std::rt::lang_start::{{closure}}
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/rt.rs:164:18
10: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/core/src/ops/function.rs:284:13
11: std::panicking::try::do_call
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/panicking.rs:554:40
12: std::panicking::try
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/panicking.rs:518:19
13: std::panic::catch_unwind
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/panic.rs:345:14
14: std::rt::lang_start_internal::{{closure}}
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/rt.rs:143:48
15: std::panicking::try::do_call
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/panicking.rs:554:40
16: std::panicking::try
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/panicking.rs:518:19
17: std::panic::catch_unwind
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/panic.rs:345:14
18: std::rt::lang_start_internal
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/rt.rs:143:20
19: std::rt::lang_start
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/rt.rs:163:17
20: _main
进程已结束,退出代码为 0
小技巧:可以不显示指定返回 anyhow::Error
。 anyhow 库的设计目的就是为了简化错误处理,它提供了 Result 类型的别名 anyhow::Result<T>
,其等价于 std::result::Result<T, anyhow::Error>
use anyhow::Result;
use std::fs::File;
use std::io::Read;
fn main() {
let res = read_file();
match res {
Ok(str) => { println!("{}", str) }
Err(err) => { println!("{:?}", err) }
}
}
fn read_file() -> Result<String> {
let mut s = String::new();
File::open("test.txt")?.read_to_string(&mut s)?;
Ok(s)
}
Result别名
pub type Result<T, E = Error> = core::result::Result<T, E>;
-
type
用于创建类型别名。借助type
关键字,可以给已有的类型定义一个新的名字,从而提升代码的可读性和可维护性。 -
T
:这是一个泛型类型参数,代表操作成功时返回的值的类型。在使用 Result 类型时,需要指定T
的具体类型。 -
E = Error
: E 同样是泛型类型参数,代表操作失败时返回的错误类型。E = Error
属于默认类型参数,若在使用 Result 时没有指定 E 的类型,就会默认使用 Error 类型。 -
core::result::Result
是 Rust 标准库中的一个枚举类型,定义如下:
enum Result<T, E> {
Ok(T),
Err(E),
}
使用Result别名的优点:
- 简化代码 :通过定义类型别名,可以用更简洁的名称来替代复杂的类型,让代码更易读。
- 统一错误处理 :借助默认错误类型参数,能在项目里统一错误处理,减少错误类型的重复定义。
错误处理
unwrap
unwarp()方法主要用来处理Result<T,E>
和Option<T>
两种类型,以Result类型为例:
- 如果期望返回的一定是
Ok(T)
值,可以使用.unwarp()
,它会提取并返回Ok(T)
包含的val值 - 如果返回的是
Err(E)
,那么.unwarp()
会触发一个运行时panic,停止程序并打印出错误信息
fn main() {
let res:String = read_file().unwrap();
println!("{}",res)
}
fn read_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
同理,如果是Option类型
- 能正常处理
Some(val)
,并返回val - 如果处理
None
则直接panic
综上所述:在生产环境不推荐随意使用.unwarp()
方法,可能会导致程序出现预期外的崩溃
expect
expect()主要用来处理Result类型:
- 如果返回
Ok(T)
,则返回Ok(T)
中包含的T值 - 如果返回
Err(E)
,则触发panic,并打印出作为参数传入的错误信息
fn main() {
let res: String = read_file().expect("第xxx行代码出现了错误,参数是x,y,z");
println!("{}", res)
}
fn read_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
输出了expect方法中传入的错误说明
thread 'main' panicked at src/main.rs:32:35:
第xxx行代码出现了错误,参数是x,y,z: Os { code: 2, kind: NotFound, message: "No such file or directory" }
stack backtrace:
0: rust_begin_unwind
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/std/src/panicking.rs:662:5
1: core::panicking::panic_fmt
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/core/src/panicking.rs:74:14
2: core::result::unwrap_failed
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/core/src/result.rs:1679:5
3: core::result::Result<T,E>::expect
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/core/src/result.rs:1059:23
4: hello_rust::main
at ./src/main.rs:32:23
5: core::ops::function::FnOnce::call_once
at /rustc/176e5452095444815207be02c16de0b1487a1b53/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
进程已结束,退出代码为 101
综上所述:使用.except()
方法通常是在开发阶段测试某个操作不应该失败的情况,在生产环境不推荐使用
unwarp_or
上文说道,.unwarp()
不建议随便使用,因为在处理Err(E)
时会导致panic,而.unwarp_or()
允许在遇到Err(E)
时返回一个默认值让程序继续往下执行
fn main() {
let res: String = read_file().unwrap_or("如果发生了错误,则返回一个默认值".to_string());
println!("---> {}", res)
}
fn read_file() -> Result<String, io::Error> {
let mut s = String::new();
// 该文件不存在
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
程序没有崩溃,而是返回了设置的默认值
/Users/kl/.cargo/bin/cargo run --color=always --package hello_rust --bin hello_rust
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/hello_rust`
---> 如果发生了错误,则返回一个默认值
进程已结束,退出代码为 0