← 中文资源

简体中文版本

Anyhow 上下文错误链

用 anyhow 给错误叠加上下文层:with_context 捕获当时的现场变量、错误链逐层打印根因、backtrace 状态可探测。

阅读英文原文

示例说明

裸错误只有一根孤零零的消息——排障时最缺的是它发生在哪。anyhow 的 context 在错误向上冒泡的每一层贴上现场说明:读文件失败时附上路径、解析端口失败时附上原始值。err.chain() 把整条链展开:最外层上下文先出,根因殿后,一张报告说清来龙去脉。

技术要点

  • with_context 惰性构造避免无谓开销
  • chain 逐层展开错误因果
  • ok_or_else 兜住 Option 的缺失场景

代码解读

load_config 给 IO 错误附路径上下文;parse_port 三层嵌套——解析错误、范围提示、原始字段值逐层包裹。bootstrap 组合两者并把缺失字段的 Option 转为 anyhow 错误。main 的错误分支遍历链打印每层原因,backtrace 状态反映是否启用了 RUST_BACKTRACE。

使用建议

context 文案写给排障的人看,要含关键变量值。库代码用 thiserror 定义类型,应用层才用 anyhow。backtrace 需要错误指针链特性开启。

代码示例

use anyhow::{anyhow, Context, Result};
use std::fs;
use std::path::Path;

fn load_config(path: &Path) -> Result<String> {
    fs::read_to_string(path)
        .with_context(|| format!("reading config from {}", path.display()))
}

fn parse_port(raw: &str) -> Result<u16> {
    raw.trim()
        .parse::<u16>()
        .context("port must be 1-65535")
        .with_context(|| format!("invalid port field: {raw:?}"))
}

fn bootstrap(path: &Path) -> Result<()> {
    let content = load_config(path)?;

    let port = content
        .lines()
        .find(|l| l.starts_with("port="))
        .and_then(|l| l.split('=').nth(1))
        .ok_or_else(|| anyhow!("config missing required field: port"))?;

    let parsed = parse_port(port)?;
    println!("bootstrap ok, listening on {parsed}");
    Ok(())
}

fn main() {
    let path = Path::new("app.conf");

    match bootstrap(path) {
        Ok(()) => println!("done"),
        Err(err) => {
            // 链式打印:每层 context 依次列出,根因最后
            let mut chain = err.chain();
            println!("error: {}", chain.next().unwrap());
            for cause in chain {
                println!("  caused by: {cause}");
            }
            println!("backtrace available: {}", err.backtrace().status().is_enabled());
        }
    }
}