[rust]错误和处理方式

49 阅读11分钟

可恢复错误(Result<T,E>)

Result是Rust中用于处理错误的核心类型之一,封装了一个成功的值或者一个错误

  1. Ok(T)表示操作成功,并包含一个类型为T的值
  2. 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

  1. 可以看到stack backtrace还标注了序号,序号3调用了序号2发生的panic,所以2的栈在3的顶上,堆栈中从上往下当前序号的上一位是自己调用的方法,下一位是调用自己的方法
  2. 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)
    }
}

通过?传播

  1. 如果有错误就直接返回错误
  2. 如果没有错误返回真实的值,并继续往下执行
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别名的优点:

  1. 简化代码 :通过定义类型别名,可以用更简洁的名称来替代复杂的类型,让代码更易读。
  2. 统一错误处理 :借助默认错误类型参数,能在项目里统一错误处理,减少错误类型的重复定义。

错误处理

unwrap

unwarp()方法主要用来处理Result<T,E>Option<T>两种类型,以Result类型为例:

  1. 如果期望返回的一定是Ok(T)值,可以使用.unwarp(),它会提取并返回Ok(T)包含的val值
  2. 如果返回的是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类型

  1. 能正常处理Some(val),并返回val
  2. 如果处理None则直接panic

综上所述:在生产环境不推荐随意使用.unwarp()方法,可能会导致程序出现预期外的崩溃

expect

expect()主要用来处理Result类型:

  1. 如果返回Ok(T),则返回Ok(T)中包含的T值
  2. 如果返回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