Rust 错误处理分层:用 thiserror 定义库错误、用 anyhow 收口应用层

test12026-09-206 次阅读

库层与应用层,错误的职责不一样

Rust 错误处理常被新手搞成一坨 anyhow::Result。更清晰的分工是:底层库/模块用 thiserror 定义成体系的错误类型,让调用方可以 match 区分;最上层的 main 或 handler 用 anyhow 收口,只关心“成不成功”以及失败时带上上下文。两层配合,既不丢失可判别性,也不让上层被错误类型淹没。

用 thiserror 定义可判别的库错误

#[from] 自动生成 From 实现,使底层 sqlx::Error 能 ? 直接上转;NotFound、InvalidInput 则是业务自己的变体,调用方可以精确匹配。Display 信息也在这里写清楚。

use thiserror::Error;

#[derive(Debug, Error)]
pub enum RepositoryError {
    #[error("数据库连接失败: {0}")]
    Db(#[from] sqlx::Error),

    #[error("记录不存在: id={0}")]
    NotFound(i64),

    #[error("参数非法: {0}")]
    InvalidInput(String),
}

pub fn find_user(id: i64) -> Result<(), RepositoryError> {
    if id <= 0 {
        return Err(RepositoryError::InvalidInput("id 必须为正".into()));
    }
    if id == 999 {
        return Err(RepositoryError::NotFound(id));
    }
    Ok(())
}

用 anyhow 在上层收口并附上下文

应用层一般不想再为每一种可能错误定义新类型。anyhow::Result 配合 with_context,能在出错时把“这一步在做什么”串进报错,排障时一眼看到失败链路。

use anyhow::{Context, Result};

fn load_config(path: &str) -> Result<String> {
    let raw = std::fs::read_to_string(path)
        .with_context(|| format!("读取配置文件失败: {path}"))?;
    Ok(raw)
}

fn main() -> Result<()> {
    let cfg = load_config("config.toml")?;
    println!("配置长度: {}", cfg.len());
    Ok(())
}

怎么选

  • 被别人依赖的 crate / 模块:用 thiserror,错误要可枚举、可 match。
  • 二进制入口、HTTP handler、脚本:用 anyhow,只问成败并附上下文。
  • 需要跨层转换时,用 #[from] 或 map_err 把底层错误提升为上层类型。
T

test1

文章作者

库层与应用层,错误的职责不一样 Rust 错误处理常被新手搞成一坨 anyhow::Result。更清晰的分工是:底层...

分类
技术
发布时间
2026-09-20
字数
约 1445 字
阅读
6 次