跳过正文

thiserror

··1812 字
Rust Rust-Crate
rust crate - 这篇文章属于一个选集。
§ 5: 本文

thiserror 使用 derive macro 来创建自定义 Error 类型,消除实现 Display/Error/From 等标准 trait 的样板式代码。

常规 Rust Error 定义方式:

  1. Result<Config, &‘static str> 的 Error 类型是字符串,需要解析内容才知道具体的错误类型和细节;
  2. Result<(), Box<dyn std::error::Error + ‘static + Send + Sync>> 是动态 Error,需要 downcase 来转换才知道具体的错误类型;
impl Config {
    // 使用 &'static str
    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };

        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a file name"),
        };

        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

        Ok(Config {
            query,
            filename,
            case_sensitive,
        })
    }

    // 使用 Box<dyn std::error::Error>
    pub fn run(config: Config) -> Result<(), Box<dyn std::error::Error>> {
        let contents = fs::read_to_string(config.filename)?;

        let results = if config.case_sensitive {
            search(&config.query, &contents)
        } else {
            search_case_insensitive(&config.query, &contents)
        };

        for line in results {
            println!("{}", line);
        }

        Ok(())
    }

标准库 std:error::Error trait:

  1. 标准库和三方库的各种自定义错误类型都需要实现的 trait,这样后续可以使用 trait object 来统一表示,如 Box<dyn std::error::Error> ;
  2. source() 返回更低一个层次的 Error, 后续在显示 Error 信息时会用 caused by 来显示;
pub trait Error: Debug + Display {
    // Provided methods
    fn source(&self) -> Option<&(dyn Error + 'static)> { ... }
    fn description(&self) -> &str { ... }
    fn cause(&self) -> Option<&dyn Error> { ... }
    fn provide<'a>(&'a self, request: &mut Request<'a>) { ... }
}

自定义错误类型例子:包含大量样板式代码

  • 一般使用 enum 类型 ,这样可以通过 variant 定义多个子错误类型和信息;
  • 如果使用 struct,则只能通过 struct field 来生成一个错误类型和信息(无子类型);
// https://betterprogramming.pub/a-simple-guide-to-using-thiserror-crate-in-rust-eee6e442409b

use std::{error::Error, fmt::Debug};

// 自定义错误类型, 封装了底层的其它错误类型( std::io::Error 等都是具体
// struct 类型而非 trait)
enum CustomError {
    FileReadError(std::io::Error),
    RequestError(reqwest::Error),
    FileDeleteError(std::io::Error),
}

// 将其它 Error 类型转换为 CustomError 类型
//
// 更好的方式是实现 From<std::error::Error>
impl From<reqwest::Error> for CustomError {
    fn from(e: reqwest::Error) -> Self {
        CustomError::RequestError(e)
    }
}

// 实现 std::error::Error, 同时自定义 source() 方法, 返回更底层的错误对象
//
// 因为大量的标准库类型实现了 std::io::Error trait,所以实现该 From trait 后,
// 它们都可以转换为 CustomError
impl std::error::Error for CustomError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        // self 是 &CustomError 类型, 所以在 match 解构时, s 是引用类型,
        // 满足返回值 &(dyn Error+'static) 的要求。
        match self {
            CustomError::FileReadError(s) => Some(s),
            CustomError::RequestError(s) => Some(s),
            CustomError::FileDeleteError(s) => Some(s),
        }
    }
}

// 由于 std::error::Error 是 Debug 和 Display 的子 trait, 所以自定义错误类型
// 还需要实现这两个 trait
impl std::fmt::Debug for CustomError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "{}", self)?;
        if let Some(source) = self.source() {
            writeln!(f, "Caused by:\n\t{}", source)?;
        }
        Ok(())
    }
}

// 根据错误类型的 variant, 显示不同的错误信息
impl std::fmt::Display for CustomError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CustomError::FileReadError(_) => write!(f, "failed to read the key file"),
            CustomError::RequestError(_) => write!(f, "failed to send the api request"),
            CustomError::FileDeleteError(_) => write!(f, "failed to delete the key file"),
        }
    }
}

// 使用自定义 CustomError
fn main() {
    println!("{:?}", make_request().unwrap_err());
}

fn make_request() -> Result<(), CustomError> {
    use CustomError::*;

    // 使用 map_err 将其它错误转换为自定义错误
    let key = std::fs::read_to_string("some-key-file").map_err(FileReadError)?;
    reqwest::blocking::get(format!("http:key/{}", key))?.error_for_status()?;
    std::fs::remove_file("some-key-file").map_err(FileDeleteError)?;
    Ok(())
}

使用 thiserror crate 来重写上面的例子, 可以大大简化自定义错误类型的样板式代码:

  • 消除了实现 impl std::error::Error, std::fmt::Display 和 From<reqwest::Error>;
  • 自定义错误消息可以引用错误信息(如字段值);

注:std::fmt::Debug trait 还是需要自定义实现。

#[source] 和 #[from] 的区别:

  1. source 是在为类型实现 std::error::Error 的 source() 方法时使用,如果为深层次错误类型用 #[source] 修饰,则 source() 方法返回该类型错误,否则返回 None。
  2. from 是生成从其它错误类型转换为自定义错误类型的 From trait,对于同一个其它错误类型只能使用一次 from;

#[error] 是在为类型实现 std::fmt::Display 时使用,用于指定该错误类型的错误信息。

use std::{error::Error, fmt::Debug};

// thiserror::Error 实现 std::error::Error trait
#[derive(thiserror::Error)]
enum CustomError {
    // #[error] 在实现 Display trait 时使用。
    #[error("failed to read the key file")]
    // #[source] 实现 impl std::error::Error for CustomeError,
    // 自定义 source() 方法。
    FileReadError(#[source] std::io::Error),

    #[error("failed to send the api request")]
    // #[from] 实现 impl From<reqwest::Error> for CustomeError
    RequestError(#[from] reqwest::Error),

    #[error("failed to delete the key file")]
    FileDeleteError(std::io::Error), // 故意不加 #[source]
}

impl Debug for CustomError {
    fn fmt(
        &self,
        f: &mut std::fmt::Formatter<'_>,
    ) -> std::fmt::Result {
        writeln!(f, "{}", self)?;
        if let Some(source) = self.source() {
            // 使用 #[source]
            writeln!(f, "Caused by:\n\t{}", source)?;
        }
        Ok(())
    }
}
}

// 使用 cargo-expand 来参开宏
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use std::{error::Error, fmt::Debug};
enum CustomError {
    #[error("failed to read the key file")]
    FileReadError(#[source] std::io::Error),
    #[error("failed to send the api request")]
    RequestError(#[from] reqwest::Error),
    #[error("failed to delete the key file")]
    FileDeleteError(std::io::Error),
}
#[allow(unused_qualifications)]
#[automatically_derived]
impl std::error::Error for CustomError {
    fn source(&self) -> ::core::option::Option<&(dyn std::error::Error + 'static)> {
        use thiserror::__private::AsDynError as _;
        #[allow(deprecated)]
        match self {
        // 包含 #[source] 时,souce() 返回修饰的其它 error 类型。否则返回 None。
            CustomError::FileReadError { 0: source, .. } => {
                ::core::option::Option::Some(source.as_dyn_error())
            }
            CustomError::RequestError { 0: source, .. } => {
                ::core::option::Option::Some(source.as_dyn_error())
            }
            CustomError::FileDeleteError { .. } => ::core::option::Option::None,
        }
    }
}
#[allow(unused_qualifications)]
#[automatically_derived]
impl ::core::fmt::Display for CustomError {
    fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        #[allow(unused_variables, deprecated, clippy::used_underscore_binding)]
        match self {
            // #[error] 被用作 Display 时的错误消息
            CustomError::FileReadError(_0) => {
                __formatter.write_str("failed to read the key file")
            }
            CustomError::RequestError(_0) => {
                __formatter.write_str("failed to send the api request")
            }
            CustomError::FileDeleteError(_0) => {
                __formatter.write_str("failed to delete the key file")
            }
        }
    }
}
#[allow(unused_qualifications)]
#[automatically_derived]
// 所有的 #[from] 都被实现为 From trait, 所以在一个错误类型中,
// 不能对同一个类型多次添加 #[from] macro:
impl ::core::convert::From<reqwest::Error> for CustomError {
    #[allow(deprecated)]
    fn from(source: reqwest::Error) -> Self {
        CustomError::RequestError {
            0: source,
        }
    }
}
impl Debug for CustomError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!("{0}\n", self))?;
        if let Some(source) = self.source() {
            f.write_fmt(format_args!("Caused by:\n\t{0}\n", source))?;
        }
        Ok(())
    }
}

注:

  1. 任何实现了 std::error::Error or dereferences to dyn std::error::Error 都可以作为 #[source], 例如 anyhow::Error 类型可以作为 source;
  2. 对同一种错误类型,#[source] 可以指定多次,但是对于 #[from] 只能指定一次。
  3. #[error(transparent)] 表示将 forward the source and Display methods straight through to an underlying error without adding an additional message;一般用于实现了 Display 的自定义 Error 类型,如 anyhow::Error.
#[derive(thiserror::Error, Debug)]
enum SimpleError {
    // #[error(transparent)] 不指定 error message,但显示对应 error
    // 类型的 Display message。
    #[error(transparent)]
    // rustc: error: #[error(transparent)] requires exactly one field
    //ErrorOther(anyhow::Error, i32),
    ErrorOther(anyhow::Error)
}

thiserror 也可以用于 struct 类型,但只能报一个错误类型信息,没有 enum 实用。 struct filed 不能使用 #[error],因为 error 是根据 enum variant 来分别显示不同的错误信息。

#[derive(thiserror::Error, Debug)]
// struct 错误类型只能在 struct 上定义一个 error message
#[error("error type1 {i} {b} {s}")]
struct SimpleError2 {
    // struct filed 不能使用 #[error]
    i: i32,
    b: bool,
    s: String,
}

// struct 错误类型的 field 如果使用 #[from], 则只能包含两个固定的 field 名称:
// source 和 backtrace.
#[derive(thiserror::Error, Debug)]
#[error("struct2: {source}")]
struct SimpleError3 {
    i: i32,
    #[from]
    source: anyhow::Error,
    // #[from]
    // i: i32, // rustc: error: deriving From requires no fields other than source and backtrace
}

// 或者使用 newtype 类型
#[derive(thiserror::Error, Debug)]
#[error("struct3: {0}")]
struct SimpleError4(#[from] anyhow::Error);
rust crate - 这篇文章属于一个选集。
§ 5: 本文

相关文章

anyhow
··1872 字
Rust Rust-Crate
anyhow crate 提供了自定义 Error 类型和 Result 类型,Error 类型自带 backtrace 和 context,支持用户友好的格式化信息输出。
bytes
··2900 字
Rust Rust-Crate
bytes 提供了高效的 zero-copy 连续内存区域的共享和读写能力。
chrono
··4003 字
Rust Rust-Crate
chrono 提供了丰富的 Date/Time 类型和相关操作。
hyper
··861 字
Rust Rust-Crate
hyper 是高性能的异步 HTTP 1/2 底层库。