对于程序开发者而言,我们常常与错误处理代码打交道。当我们需要创建自定义错误类型时,常常需要用到thiserror
库,可以帮助我们极大的简化代码。
thiserror
库为std::error::Error
trait 提供了一系列方便的派生宏,可以帮助我们简化 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("...")]
信息,那么会自动为该错误生成Display
trait 实现。如下面示例所示:
这些消息支持从错误中插入相应的字段值:
#[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]
属性的变体自动实现From
trait。
请注意,变体不得包含源错误之外的任何字段,也可能包含错误回溯。
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
的字段,就会自动实现Error
trait的provide()
方法。
use std::backtrace::Backtrace;
#[derive(Error, Debug)]
pub struct MyError {
msg: String,
backtrace: Backtrace, // automatically detected
}
如果一个字段既是一个source(字段名为source
,或者有#[source]
或#[from]
标记),并且被标记了#[backtrace]
,那么Error
trait的provide()
方法会转发到源错误,以便两层错误能够共享相同的错误回溯信息。
#[derive(Error, Debug)]
pub enum MyError {
Io {
#[backtrace]
source: io::Error,
},
}
#[error(transparent)]
可以通过 #[error(transparent)]
让 source
和 Display
直接使用底层的错误,这对于那些想处理任何的枚举来说是很有用的:
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);
}