示例说明
裸错误只有一根孤零零的消息——排障时最缺的是它发生在哪。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());
}
}
}