不灭的焱

革命尚未成功,同志仍须努力 下载Java21

作者:AlbertWen  添加时间:2026-07-09 15:58:00  修改时间:2026-07-22 21:27:39  分类:01.Rust编程  编辑

目录

仓库地址:https://crates.io/crates/anyhow

下面给你一份 Rust anyhow 详细使用教程

anyhow 主要用于 应用程序层面的错误处理,它提供了 anyhow::Erroranyhow::Result<T>,可以让你在业务代码、命令行工具、Web 后端、脚本型程序里更轻松地处理各种错误。官方文档说明,anyhow::Error 是一个基于 trait object 的错误类型,适合 Rust 应用程序中进行惯用的错误处理;anyhow::Result<T> 等价于 Result<T, anyhow::Error>。(文档.rs)

1. 安装 anyhow

Cargo.toml 中添加:

[dependencies]
anyhow = "1.0"

或者使用命令:

cargo add anyhow

当前 docs.rs 上 anyhow 最新文档版本显示为 1.0.103,但实际项目里一般写 1.0 即可,让 Cargo 自动选择兼容版本。(文档.rs)

2. anyhow 解决什么问题?

普通 Rust 代码里,经常会遇到不同来源的错误:

std::io::Error
serde_json::Error
parseIntError
sqlx::Error
sea_orm::DbErr

如果不用 anyhow,你可能需要自己定义一个大枚举:

enum AppError {
    Io(std::io::Error),
    Json(serde_json::Error),
    Db(sea_orm::DbErr),
}

这在大型项目里是合理的,但在应用层、工具脚本、后台任务中会比较繁琐。

anyhow 的目标是:我不关心错误的精确类型,只想把错误一路往上传,并附加清晰的上下文信息

3. 最基本用法:anyhow::Result

标准库的 Result<T, E> 不需要引入,但是 anyhow::Result<T> 是第三方库提供的类型别名,所以需要:

use anyhow::Result;

示例:

use anyhow::Result;

fn read_config() -> Result<String> {
    let content = std::fs::read_to_string("config.toml")?;
    Ok(content)
}

fn main() -> Result<()> {
    let config = read_config()?;
    println!("{}", config);
    Ok(())
}

这里的:

Result<String>

其实等价于:

std::result::Result<String, anyhow::Error>

官方文档也建议在可能失败的函数中使用 Result<T, anyhow::Error>,或者等价的 anyhow::Result<T>,并且可以用 ? 传播实现了 std::error::Error 的错误。(文档.rs)

4. ? 自动转换错误

假设我们既读取文件,又解析 JSON:

[dependencies]
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

代码:

use anyhow::Result;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct AppConfig {
    name: String,
    port: u16,
}

fn load_config() -> Result<AppConfig> {
    let content = std::fs::read_to_string("config.json")?;
    let config: AppConfig = serde_json::from_str(&content)?;
    Ok(config)
}

fn main() -> Result<()> {
    let config = load_config()?;
    println!("{:#?}", config);
    Ok(())
}

这里有两种错误:

std::io::Error
serde_json::Error

但是函数只返回:

anyhow::Result<AppConfig>

anyhow 会把这些错误统一包装成 anyhow::Error

5. 最重要的功能:添加上下文 context

只用 ? 有一个问题:错误信息可能太底层。

比如文件不存在,直接报:

No such file or directory

你不知道是哪个业务步骤失败了。

所以 anyhow 非常推荐加上下文:

use anyhow::{Context, Result};

fn read_config() -> Result<String> {
    let content = std::fs::read_to_string("config.toml")
        .context("读取配置文件 config.toml 失败")?;

    Ok(content)
}

fn main() -> Result<()> {
    let content = read_config()?;
    println!("{}", content);
    Ok(())
}

注意这里需要引入:

use anyhow::Context;

官方文档说明,context 可以为底层错误添加更容易排查问题的业务上下文,比如让“文件不存在”变成“读取某个配置文件失败,因为文件不存在”。(文档.rs)

6. contextwith_context 的区别

6.1 context

适合固定文本:

use anyhow::{Context, Result};

fn read_file() -> Result<String> {
    std::fs::read_to_string("app.toml")
        .context("读取 app.toml 配置文件失败")
}

6.2 with_context

适合需要动态拼接字符串的场景:

use anyhow::{Context, Result};

fn read_file(path: &str) -> Result<String> {
    std::fs::read_to_string(path)
        .with_context(|| format!("读取文件失败: {}", path))
}

with_context 的闭包只有在真正发生错误时才会执行;官方文档也明确说明,它是惰性添加上下文,只有错误发生时才求值。(文档.rs)

7. 创建临时错误:anyhow!

有时候不是来自系统错误,而是你自己判断业务不合法:

use anyhow::{anyhow, Result};

fn get_user(id: u64) -> Result<String> {
    if id == 0 {
        return Err(anyhow!("用户 ID 不能为 0"));
    }

    Ok(format!("user-{}", id))
}

fn main() -> Result<()> {
    let user = get_user(0)?;
    println!("{}", user);
    Ok(())
}

anyhow! 可以快速构造一个 anyhow::Error。官方文档说明,临时错误消息可以用 anyhow! 构造,并且支持字符串插值。(文档.rs)

8. 提前返回错误:bail!

下面这段:

return Err(anyhow!("用户 ID 不能为 0"));

可以简化成:

bail!("用户 ID 不能为 0");

完整示例:

use anyhow::{bail, Result};

fn check_age(age: u8) -> Result<()> {
    if age < 18 {
        bail!("年龄必须大于等于 18,当前年龄: {}", age);
    }

    Ok(())
}

fn main() -> Result<()> {
    check_age(16)?;
    Ok(())
}

bail! 的作用就是 立即返回一个错误,官方文档也说明它等价于 return Err(anyhow!(...))。(文档.rs)

9. 条件校验:ensure!

ensure! 类似于 assert!,但是区别很重要:

assert!(条件);

条件不满足时会 panic。

而:

ensure!(条件, "错误信息");

条件不满足时会返回 Err,不会 panic。

示例:

use anyhow::{ensure, Result};

fn create_user(username: &str, age: u8) -> Result<()> {
    ensure!(!username.trim().is_empty(), "用户名不能为空");
    ensure!(age >= 18, "用户年龄不能小于 18,当前年龄: {}", age);

    println!("创建用户成功: {}", username);
    Ok(())
}

fn main() -> Result<()> {
    create_user("", 16)?;
    Ok(())
}

官方文档说明,ensure! 在条件不满足时提前返回错误,行为类似 assert!,但它返回错误而不是 panic。(文档.rs)

10. 常用导入方式

一般项目里常见这样写:

use anyhow::{anyhow, bail, ensure, Context, Result};

分别对应:

Result      // anyhow::Result<T>
Context     // .context() 和 .with_context()
anyhow!     // 构造临时错误
bail!       // 立即 return Err
ensure!     // 条件不满足时 return Err

11. 一个完整案例:读取配置文件并校验

目录:

demo-anyhow/
├── Cargo.toml
├── config.json
└── src/
    └── main.rs

Cargo.toml

[package]
name = "demo-anyhow"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

config.json

{
  "app_name": "demo-api",
  "port": 8080,
  "database_url": "mysql://root:123456@localhost:3306/demo"
}

src/main.rs

use anyhow::{ensure, Context, Result};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct AppConfig {
    app_name: String,
    port: u16,
    database_url: String,
}

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

    let config: AppConfig = serde_json::from_str(&content)
        .with_context(|| format!("解析 JSON 配置文件失败: {}", path))?;

    validate_config(&config)?;

    Ok(config)
}

fn validate_config(config: &AppConfig) -> Result<()> {
    ensure!(
        !config.app_name.trim().is_empty(),
        "app_name 不能为空"
    );

    ensure!(
        config.port > 0,
        "port 必须大于 0,当前值: {}",
        config.port
    );

    ensure!(
        config.database_url.starts_with("mysql://"),
        "database_url 必须是 MySQL 连接地址,当前值: {}",
        config.database_url
    );

    Ok(())
}

fn main() -> Result<()> {
    let config = load_config("config.json")?;
    println!("配置加载成功: {:#?}", config);
    Ok(())
}

这个例子里:

std::fs::read_to_string(path)?

可能产生文件读取错误。

serde_json::from_str(&content)?

可能产生 JSON 解析错误。

ensure!(...)

可能产生业务校验错误。

但它们都被统一成了:

anyhow::Result<T>

12. anyhow 和普通 Result 的区别

普通标准库写法:

fn read_file() -> std::result::Result<String, std::io::Error> {
    let content = std::fs::read_to_string("a.txt")?;
    Ok(content)
}

anyhow 写法:

use anyhow::Result;

fn read_file() -> Result<String> {
    let content = std::fs::read_to_string("a.txt")?;
    Ok(content)
}

区别是:

std::result::Result<T, E>

需要你明确写错误类型 E

而:

anyhow::Result<T>

默认错误类型就是:

anyhow::Error

所以它只有一个泛型参数 T

13. 打印错误信息

示例:

use anyhow::{Context, Result};

fn run() -> Result<()> {
    std::fs::read_to_string("missing.txt")
        .context("读取 missing.txt 失败")?;

    Ok(())
}

fn main() {
    if let Err(err) = run() {
        eprintln!("普通显示: {}", err);
        eprintln!("带原因链显示: {:#}", err);
        eprintln!("Debug 显示: {:?}", err);
    }
}

几种打印方式:

{}

只打印最外层错误。

{:#}

打印错误和原因链。

{:?}

Debug 格式,可能包含 backtrace。

官方文档说明,{} 只打印最外层错误或上下文;{:#} 会包含原因链;{:?} 在捕获了 backtrace 时会包含 backtrace 信息。(文档.rs)

14. 查看错误链 chain()

你也可以手动遍历错误链:

use anyhow::{Context, Result};

fn run() -> Result<()> {
    std::fs::read_to_string("missing.txt")
        .context("读取配置文件失败")?;

    Ok(())
}

fn main() {
    if let Err(err) = run() {
        eprintln!("错误: {}", err);

        for cause in err.chain().skip(1) {
            eprintln!("原因: {}", cause);
        }
    }
}

适合你想自定义日志格式的时候。

15. backtrace 回溯

如果想看到错误发生的调用栈,可以设置环境变量。

Linux / macOS:

RUST_BACKTRACE=1 cargo run

或者只给错误开启 backtrace:

RUST_LIB_BACKTRACE=1 cargo run

Windows PowerShell:

$env:RUST_BACKTRACE=1
cargo run

官方文档说明,Rust 1.65 及以上,如果底层错误类型没有自己的 backtrace,anyhow 可以捕获并打印 backtrace;可以通过 RUST_BACKTRACERUST_LIB_BACKTRACE 控制显示。(文档.rs)

16. 和 thiserror 搭配使用

通常建议:

anyhow    // 应用层错误处理
thiserror // 定义明确的业务错误类型

例如:

[dependencies]
anyhow = "1.0"
thiserror = "2"

代码:

use anyhow::{Context, Result};
use thiserror::Error;

#[derive(Debug, Error)]
enum UserError {
    #[error("用户不存在,id: {0}")]
    NotFound(u64),

    #[error("用户状态异常: {0}")]
    InvalidStatus(String),
}

fn find_user(id: u64) -> std::result::Result<String, UserError> {
    if id == 0 {
        return Err(UserError::NotFound(id));
    }

    Ok(format!("user-{}", id))
}

fn run() -> Result<()> {
    let user = find_user(0)
        .context("查询用户失败")?;

    println!("用户: {}", user);
    Ok(())
}

fn main() -> Result<()> {
    run()
}

find_user 返回明确的业务错误:

std::result::Result<String, UserError>

应用层 run 使用:

anyhow::Result<()>

这样底层错误类型明确,上层调用又很方便。

官方文档也说明,anyhow 本身不提供 derive(Error) 宏,如果需要自定义错误类型,可以手写 std::error::Error 实现,或者使用 thiserror 这类独立宏库。(文档.rs)

17. 在 Web 项目中怎么用?

比如你做 Rust Web 后端,推荐分层使用:

handler 层:转换成 HTTP 响应
service 层:可以用 anyhow::Result
repository 层:返回数据库错误或 anyhow::Result

示例:

use anyhow::{Context, Result};

struct User {
    id: u64,
    name: String,
}

async fn query_user_from_db(id: u64) -> Result<User> {
    // 这里假设是真实数据库查询
    if id == 0 {
        anyhow::bail!("用户 ID 不能为 0");
    }

    Ok(User {
        id,
        name: "Albert".to_string(),
    })
}

async fn get_user_service(id: u64) -> Result<User> {
    let user = query_user_from_db(id)
        .await
        .with_context(|| format!("查询用户失败,id={}", id))?;

    Ok(user)
}

然后在 handler 层不要直接把 anyhow::Error 原样暴露给前端,而是转成统一响应:

async fn handler() {
    match get_user_service(0).await {
        Ok(user) => {
            println!("返回用户: {}", user.name);
        }
        Err(err) => {
            eprintln!("内部错误: {:?}", err);

            // 返回给前端时建议统一成:
            // {"code": 500, "message": "服务器内部错误"}
        }
    }
}

原因是:anyhow 的错误信息通常包含内部细节,比如文件路径、SQL、配置项、调用链等,不应该直接返回给用户。

18. 在 SeaORM / MySQL 项目中的用法

你之前经常问 Salvo + SeaORM + MySQL,这种项目里可以这样用:

use anyhow::{Context, Result};
use sea_orm::{Database, DatabaseConnection};

pub async fn connect_db(database_url: &str) -> Result<DatabaseConnection> {
    let db = Database::connect(database_url)
        .await
        .with_context(|| format!("连接数据库失败: {}", mask_db_url(database_url)))?;

    Ok(db)
}

fn mask_db_url(url: &str) -> String {
    // 简单脱敏示例,真实项目可以做得更完善
    url.replace("root:123456", "root:******")
}

注意这里不要把数据库密码原样写进错误上下文,否则日志里会泄露密码。

更好的写法:

use anyhow::{Context, Result};
use sea_orm::{Database, DatabaseConnection};

pub async fn connect_db(database_url: &str) -> Result<DatabaseConnection> {
    let db = Database::connect(database_url)
        .await
        .context("连接 MySQL 数据库失败")?;

    Ok(db)
}

19. 什么时候适合用 anyhow?

适合:

命令行工具
后台任务
应用程序 main 函数
Web 项目的 service 层
快速开发阶段
内部工具
测试代码
脚本型程序

不太适合:

公共库的公开 API
需要让调用者精确 match 错误类型的场景
需要严格区分错误码的业务核心层

例如公共库最好不要这样:

pub fn parse_config() -> anyhow::Result<Config> {
    // ...
}

因为别人调用你的库时,很难知道具体会返回哪些错误。

公共库更推荐:

pub fn parse_config() -> Result<Config, ConfigError> {
    // ...
}

其中 ConfigError 可以用 thiserror 定义。

20. 常见错误写法

错误 1:忘记引入 Context

use anyhow::Result;

fn read_file() -> Result<String> {
    std::fs::read_to_string("a.txt")
        .context("读取失败")
}

会报错,因为 .context() 来自 Context trait。

正确:

use anyhow::{Context, Result};

fn read_file() -> Result<String> {
    std::fs::read_to_string("a.txt")
        .context("读取失败")
}

错误 2:把 anyhow::Result 当成标准库 Result 用

错误:

use anyhow::Result;

fn demo() -> Result<String, std::io::Error> {
    Ok("hello".to_string())
}

anyhow::Result<T> 只有一个泛型参数。

正确写法 1:

use anyhow::Result;

fn demo() -> Result<String> {
    Ok("hello".to_string())
}

正确写法 2:

fn demo() -> std::result::Result<String, std::io::Error> {
    Ok("hello".to_string())
}

错误 3:滥用 unwrap

不推荐:

let content = std::fs::read_to_string("config.toml").unwrap();

推荐:

use anyhow::{Context, Result};

fn read_config() -> Result<String> {
    let content = std::fs::read_to_string("config.toml")
        .context("读取 config.toml 失败")?;

    Ok(content)
}

21. 推荐项目写法

可以在项目里定义一个统一 prelude:

// src/prelude.rs
pub use anyhow::{anyhow, bail, ensure, Context, Result};

然后业务代码里:

use crate::prelude::*;

fn demo() -> Result<()> {
    ensure!(1 + 1 == 2, "数学出错了");
    Ok(())
}

如果是多模块项目,这种写法很舒服。

22. 总结口诀

anyhow 可以这样记:

Result<T>        // 统一错误返回类型
?                // 自动向上传播错误
context          // 给错误加固定上下文
with_context     // 给错误加动态上下文
anyhow!          // 手动创建错误
bail!            // 直接 return Err
ensure!          // 条件不满足就 return Err

实际开发中最常用的是:

use anyhow::{Context, Result};

其次是:

use anyhow::{anyhow, bail, ensure};

对于你的 Rust Web 后端项目,我建议:

应用层 / service 层:anyhow::Result
领域错误 / 公共库:thiserror 自定义错误
handler 层:把 anyhow::Error 转换成统一 HTTP 响应
日志层:打印 {:#} 或 {:?}
返回前端:不要直接返回 anyhow 的内部错误