使用thiserror和anyhow时如何保留错误上下文?
解决自定义错误与上下文扩展的冲突问题
核心问题根源
一旦用anyhow::context给错误添加上下文,错误会被包装成anyhow::Error类型——这个通用错误容器不了解你用thiserror定义的自定义枚举结构,自然无法自动映射回去。直接实现From<anyhow::Error>又会丢失原始错误的类型信息,导致上下文和具体错误变体的关联断裂。
解决方案1:基于thiserror扩展上下文
无需依赖anyhow,给自定义错误枚举添加带上下文的变体,或为原有变体增加上下文字段:
方式A:通用上下文变体
use thiserror::Error; #[derive(Error, Debug)] pub enum BufferPoolError { #[error("buffer exhausted")] BufferExhausted, #[error("split table failed: {0}")] SplitTableError(#[from] ExtendibleHashTableSplitErrors), #[error("{context}: {source}")] WithContext { context: String, source: Box<dyn std::error::Error + Send + Sync>, }, } // 定义扩展trait简化上下文添加逻辑 pub trait AddContext<T> { fn with_context<C: Into<String>>(self, context: C) -> Result<T, BufferPoolError>; } impl<T, E: std::error::Error + Send + Sync + 'static> AddContext<T> for Result<T, E> { fn with_context<C: Into<String>>(self, context: C) -> Result<T, BufferPoolError> { self.map_err(|source| BufferPoolError::WithContext { context: context.into(), source: Box::new(source), }) } }
方式B:为特定变体添加上下文字段
如果需要更精准的上下文关联,直接给目标变体增加字段:
#[derive(Error, Debug)] pub enum BufferPoolError { #[error("buffer exhausted: {reason}")] BufferExhausted { reason: String }, #[error("split table failed: {source} (context: {context})")] SplitTableError { source: ExtendibleHashTableSplitErrors, context: String, }, } // 使用示例 fn allocate_buffer() -> Result<(), BufferPoolError> { Err(BufferPoolError::BufferExhausted { reason: "requested 8 buffers, only 3 available".into(), }) }
解决方案2:正确使用snafu实现上下文
snafu的核心是通过错误变体的字段承载上下文,而非依赖外部包装。重新定义错误枚举:
use snafu::Snafu; #[derive(Debug, Snafu)] pub enum BufferPoolError { #[snafu(display("buffer exhausted: {reason}"))] BufferExhausted { reason: String }, #[snafu(display("split table failed: {source} (context: {context})"))] SplitTableError { source: ExtendibleHashTableSplitErrors, context: String, }, } // 使用示例:链式添加上下文 fn split_table() -> Result<(), BufferPoolError> { // 假设split_inner返回ExtendibleHashTableSplitErrors split_inner().context(SplitTableErrorSnafu { context: "trying to split bucket 42".into(), }) }
关键注意事项
- 避免混用
anyhow和自定义错误枚举:anyhow适合快速原型或无需精准错误匹配的场景,自定义错误则用于需要精准处理错误的业务逻辑。 - 上下文要与错误变体绑定:无论是
thiserror还是snafu,上下文都应作为错误变体的一部分存在,而非用外部容器包装,这样才能保留完整的错误类型信息。
内容的提问来源于stack exchange,提问作者Raz Luvaton
相关产品推荐
相关产品推荐

