thiserror 使用 derive macro 来创建自定义 Error 类型,消除实现 std::error::Error trait 时的样板式代
码。
常规 Error 定义方式 #
Result<Config, &'static str>:静态字符串 Error,需要解析内容才知道具体的错误类型和细节;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>
// Rust 提供了标准库 Error 到 Box<dyn std::error::Error> 的 From trait 转换实现。
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(())
}
自定义错误类型 #
包含大量样板式代码:
- 一般使用 enum 类型 ,这样可以通过 variant 定义 多个 子错误类型和信息;
- 如果使用 struct,则只能生成一个错误类型和信息(无子类型);
// https://betterprogramming.pub/a-simple-guide-to-using-thiserror-crate-in-rust-eee6e442409b
use std::{error::Error, fmt::Debug};
// 自定义错误类型, 封装了底层的其它错误类型( std::io::Error 等都是实现 std::error::Error trait 的具体 struct 类型)
enum CustomError {
FileReadError(std::io::Error),
RequestError(reqwest::Error),
FileDeleteError(std::io::Error),
}
// 将其它 Error 类型转换为 CustomError 类型。
// 更好的方式是实现 From<std::error::Error>,因为标准库类型实现了 std::io::Error trait,所以实现该 From trait 后,它们都可以转换为 CustomError。
impl From<reqwest::Error> for CustomError {
fn from(e: reqwest::Error) -> Self {
CustomError::RequestError(e)
}
}
// 实现 std::error::Error
// 自定义 source() 方法, 返回更底层的错误对象。
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 {
// 先调用 Display trait 的实现,显示错误信息
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 #
使用 thiserror crate 来简化创建自定义 Error 类型的样板式代码:
- 消除了实现
std::error::Error, std::fmt::Display和From<reqwest::Error>的样板式代码; - 自定义错误消息可以引用错误信息(如字段值);
字段引用语法:
- #[error("{var}")] ⟶ write!("{}", self.var)
- #[error("{0}")] ⟶ write!("{}", self.0)
- #[error("{var:?}")] ⟶ write!("{:?}", self.var)
- #[error("{0:?}")] ⟶ write!("{:?}", self.0)
// 导入 Error derive 宏
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,
}
#[derive(Error, Debug)]
pub enum Error {
// 函数和 tuple 字段引用
#[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 },
}
注:std::fmt::Debug trait 还需要手动或通过 #[derive(debug] 宏来自动实现。
#[source]/#[from]/#[error] #
-
#[source]是在为类型实现std::error::Error的source()方法时使用,在后续使用自动实现的 Debug trait 打 印时会打印修饰的深层次错误原因(如果没有这个修饰则 source() 方法返回 None)。 -
#[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 时的自定义 source() 方法中使用。
FileReadError(#[source] std::io::Error),
#[error("failed to send the api request")]
// #[from] 包含 #[source] 语义,实现其它类型转换 impl From<reqwest::Error> for CustomeError
RequestError(#[from] reqwest::Error),
#[error("failed to delete the key file")]
FileDeleteError(std::io::Error), // 故意不加 #[source]
}
// 手动实现 Debug trait
impl Debug for CustomError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result {
// 使用实现的 Display trait 来打印 #[error] 实现的错误错误消息
writeln!(f, "{}", self)?;
// 进一步使用 #[source] 来打印更深层次的错误原因
if let Some(source) = self.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())
}
// #[from] 隐含 #[source] 语义
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]:
impl ::core::convert::From<reqwest::Error> for CustomError {
#[allow(deprecated)]
fn from(source: reqwest::Error) -> Self {
CustomError::RequestError {
0: source,
}
}
}
// 在实现 Debug trait 时,先打印 Caused by: 一行,然后打印 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(())
}
}
注:
-
任何实现了
std::error::Error或dyn std::error::Error都可以作为#[source], 例如anyhow::Error类型可以作为 source; -
对同一种错误类型,
#[source]可以指定多次,但是对于#[from]只能指定一次。
#[error(transparent)] #
在实现 source 和 Display 方法时直接使用底层的 error 对象,而不添加额外的 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)
}
struct 类型使用 thiserror #
对于 struct 类型,只能表示一个错误类型信息,故一般更多使用 enum 来表示错误类型。
struct 的 field 不能使用 #[error],因为 error 是根据 enum variant 来分别显示不同的错误信息。
// struct 错误类型只能在 struct 上定义一个 error message
#[derive(thiserror::Error, Debug)]
#[error("error type1 {i} {b} {s}")]
struct SimpleError2 {
// struct filed 不能使用 #[error]
i: i32,
b: bool,
s: String,
}
// struct 错误类型的 field 如果使用 #[from], 则只能包含两个固定的 field 名称: source 或 backtrace,或者使用 newtype 类型
#[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);