背景

Rust 的 Result 很强,但很多项目还是会遇到同一个问题:

  • 错误被一路 ? 传上去
  • 日志里只有一个模糊报错
  • 出问题时不知道是哪个环节失败

这通常是错误分层没有做好。

一条实用原则

  • 库层定义结构化错误类型
  • 应用层补充上下文并统一输出
use thiserror::Error;

#[derive(Debug, Error)]
pub enum RepoError {
    #[error("record not found")]
    NotFound,
    #[error("db error: {0}")]
    Database(String),
}
use anyhow::{Context, Result};

pub async fn get_user_handler(id: i64, svc: &UserService) -> Result<UserDto> {
    let user = svc
        .find_user(id)
        .await
        .with_context(|| format!("get user failed, id={id}"))?;

    Ok(UserDto::from(user))
}

日志里要带可关联字段

只打印错误文本通常不够,至少带上:

  • 请求 ID
  • 用户或租户标识
  • 关键资源 ID
tracing::error!(
    request_id = %request_id,
    user_id = user_id,
    error = %err,
    "failed to get user"
);

总结

Rust 错误处理做得好,排障效率会明显提升。

核心不是“有没有 ?”,而是:

  • 错误类型是否表达业务语义
  • 上下文是否在边界处补全
  • 日志是否可检索、可关联

好的错误信息,是线上稳定性的一部分。