thiserror:Rust库层错误处理的derive方案,5.4k Star

0 阅读3分钟

thiserror:Rust库层错误处理的derive方案,5.4k Star

David Tolnay 的 thiserror 在 GitHub 上获得了 5,425 个 Star。这个库通过 derive 宏自动生成 std::error::Error trait 的实现,开发者无需手写 Display 转换和 source 追溯逻辑。

在 Rust 中定义自定义错误类型,通常需要为每个变体手动实现 Display 和 Error trait。枚举变体一多,样板代码会快速膨胀,出错概率也同步增加。thiserror 在编译期通过过程宏生成这些实现,写出来的代码紧凑,且不暴露到公共 API 中。

正文顶部截图

核心用法是在枚举或结构体上添加 #[derive(Error)],然后用 #[error("...")] 为每个变体指定错误消息:

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("...")] 中的消息支持字段插值。{var} 对应 self.var 的 Display 输出,{var:?} 对应 Debug 输出。元组结构体用 {0} 引用位置字段。插值后方可追加额外的格式化参数,参数可以是任意表达式:

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

外部表达式中引用字段时,命名字段用 .var,元组字段用 .0

#[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 },
}

README区域截图

thiserror 还处理了 Error trait 中 Display 以外的几个方法:

source() 方法自动指向标记了 #[source]#[from] 的字段。#[from] 属性同时为对应变体生成 From 实现,错误传播只需 ? 就能完成类型转换。带有 #[from] 的变体不能包含源头错误之外的其他字段,backtrace 例外。

需要透传底层错误时,#[error(transparent)] 将 Display 和 source 方法委托给内层错误,不添加额外消息。适合枚举中的兜底变体,或者将内部表示隐藏在公开类型后面,保持 API 兼容性的同时自由调整实现。

在 1.73 以上版本的 nightly 编译器上,thiserror 支持通过 Backtrace 类型自动捕获回溯。如果某字段既是 source 又被标记为 #[backtrace]provide() 会转发到 source 的同名方法,错误链中多层共享同一个回溯。

thiserror 的设计核心是不出现在公共 API 中。使用 thiserror 生成的结果与手写实现等价,在两者间切换不构成破坏性变更。库作者可以在不担心下游兼容性的前提下引入。

与同一个作者开发的 anyhow 对比,thiserror 面向库代码,适合需要为调用方提供明确错误信息的设计。anyhow 面向应用代码,适合不关心具体错误类型、只希望方便传播错误的场景。两者分工明确:库用 thiserror,应用用 anyhow。

引入方式:

[dependencies]
thiserror = "2"

对于 Rust 生态中需要定义专用错误类型的项目,thiserror 将错误处理的样板代码压缩到几行属性宏。5,425 个 Star 和广泛的社区使用,证明这套 derive 方案已成为 Rust 错误处理中的常用组件。