对于程序开发者而言,我们常常与错误处理代码打交道。当我们需要创建自定义错误类型时,常常需要用到thiserror库,可以帮助我们极大的简化代码。

thiserror库为std::error::Errortrait 提供了一系列方便的派生宏,可以帮助我们简化 Rust 中自定义错误的创建与处理。

快速使用

首先我们需要在我们的项目中引入thiserror库。

命令行方式添加:

cargo add serde

你也可以直接编辑Cargo.toml文件,并添加以下内容:

[dependencies]
thiserror = "1.0"

自定义错误示例:

use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("data store disconnected")]
    Disconnect(#[from] io::Error),
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
    #[error("invalid header (expected {expected:?}, found {found:?})")]
    InvalidHeader { expected: String, found: String },
    #[error("unknown data store error")]
    Unknown,
}

详细用法

错误信息格式化

首先自定义的错误可以是一个枚举体、具名结构体、元组结构体、单元结构体。

如果您在结构体或者枚举体的各个变体上面添加了#[error("...")]信息,那么会自动为该错误生成Displaytrait 实现。如下面示例所示:

这些消息支持从错误中插入相应的字段值:

  • #[error("{var}")] ⟶ write!("{}", self.var)
  • #[error("{0}")] ⟶ write!("{}", self.0)
  • #[error("{var:?}")] ⟶ write!("{:?}", self.var)
  • #[error("{0:?}")] ⟶ write!("{:?}", self.0)
use thiserror::Error;

// 枚举体
#[derive(Error, Debug)]
pub enum MyError1 {
    #[error("unknown data store error")]
    Unknown,
}

// 具名结构体
#[derive(Debug, Error)]
#[error("MyError2: code={code}, message={message}")]
pub struct MyError2 {
    code: i32,
    message: String,
}

// 元组结构体
#[derive(Error, Debug)]
#[error("MyError3: code={0}, message={1}")]
pub struct MyError3(i32, String);

// 单元结构体
#[derive(Error, Debug)]
#[error("MyError4")]
pub struct MyError4;

fn main() {
    let e1 = MyError1::Unknown;
    let e2 = MyError2 {
        code: 100,
        message: "error message".to_string(),
    };
    let e3 = MyError3(200, "error message".to_string());
    let e4 = MyError4;

    // 输出:unknown data store error
    println!("{}", e1);
    // 输出:MyError2: code=100, message=error message
    println!("{}", e2);
    // 输出:MyError3: code=200, message=error message
    println!("{}", e3);
    // 输出:MyError4
    println!("{}", e4);
}

这些插入值的插槽也可以配合任何其他格式化参数一起使用,其他参数可能是任意表达式,例如:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
    #[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)]
    InvalidLookahead(u32),
}

fn main() {
    let err = Error::InvalidLookahead(42);
    // 输出:invalid rdo_lookahead_frames 42 (expected < 2147483647)
    println!("{}", err);
}

如果附加表达式参数之一需要引用结构体或枚举体的字段,则将命名字段引用为.var,如果是引用元组字段可以用.0

use thiserror::Error;

#[derive(Debug)]
pub struct Limits {
    lo: usize,
    hi: usize,
}

fn first_char(s: &str) -> char {
    s.chars().next().unwrap()
}

#[derive(Error, Debug)]
pub enum Error {
    #[error("first letter must be lowercase but was {:?}", first_char(.0))]
    WrongCase(String),
    #[error("invalid index {idx}, expected at least {} and at most {}", .limits.lo, .limits.hi)]
    OutOfBounds { idx: usize, limits: Limits },
}

fn main() {
    let error1 = Error::WrongCase("Hello".to_string());
    let error2 = Error::OutOfBounds {
        idx: 5,
        limits: Limits { lo: 0, hi: 4 },
    };
    // 输出:first letter must be lowercase but was 'H'
    println!("{}", error1);
    // 输出:invalid index 5, expected at least 0 and at most 4
    println!("{}", error2);
}

#[from]从其他错误生成

自动为每个包含#[from]属性的变体自动实现Fromtrait。

请注意,变体不得包含源错误之外的任何字段,也可能包含错误回溯。

use std::{backtrace::Backtrace, io};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("some io error happened, {:?}", .source)]
    Io {
        #[from]
        source: io::Error,
        backtrace: Backtrace,
    },
}

#[source]源错误获取

可以使用 #[source] 属性,或者将字段命名为 source,可为自定义错误实现 source 方法,返回底层的错误类型:

#[from]属性始终意味着同一字段是#[source],所以你不需要同时指定这两个属性,两者二选一即可。

use std::error::Error;
use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("some io error happened, {:?}", .err)]
    IO {
        #[source]
        err: io::Error,
    },
}

fn main() {
    let err = MyError::IO {
        err: io::Error::from(io::ErrorKind::TimedOut),
    };
    // 输出:Some(Kind(TimedOut))
    println!("{:?}", err.source());
}

错误回溯

如果存在一个类型名为Backtrace的字段,就会自动实现Errortrait的provide()方法。

use std::backtrace::Backtrace;

#[derive(Error, Debug)]
pub struct MyError {
    msg: String,
    backtrace: Backtrace,  // automatically detected
}

如果一个字段既是一个source(字段名为source,或者有#[source]#[from]标记),并且被标记了#[backtrace],那么Errortrait的provide()方法会转发到源错误,以便两层错误能够共享相同的错误回溯信息。

#[derive(Error, Debug)]
pub enum MyError {
    Io {
        #[backtrace]
        source: io::Error,
    },
}

#[error(transparent)]

可以通过 #[error(transparent)]sourceDisplay 直接使用底层的错误,这对于那些想处理任何的枚举来说是很有用的:

use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("file not found")]
    FileNotFound,
    #[error(transparent)]
    Other(#[from] io::Error), // source and Display delegate to anyhow::Error
}

fn main() {
    let err = MyError::from(io::Error::from(io::ErrorKind::TimedOut));
    // 输出: timed out
    println!("{}", err);
}