错误处理
约 908 字大约 3 分钟
2025-12-11
anyhow
应用层的通用错误容器,适合 CLI 或大型服务的顶层错误处理,减少自定义错误枚举的样板
用法
- 函数返回
anyhow::Result<T>后,即可在内部使用?来传播任意实现了std::error::Error且满足线程安全/生命周期约束的错误类型,anyhow 会把它们统一擦除成anyhow::Error - 可能失败的调用后链
.context("doing what")或.with_context(|| format!(...))来自定义错误信息 - 拿到
anyhow::Error后,如需具体类型。可以调用downcast::<T>()或downcast_ref::<T>()判断或取出具体错误
基本使用
use anyhow::{Context, Result};
fn load(path: &str) -> Result<String> {
std::fs::read_to_string(path)
.with_context(|| format!("reading config from {path}"))
}
fn parse_number(s: &str) -> Result<f64> {
s.parse::<f64>()
.with_context(|| format!("parse number from input: {s}"))
}
fn main() -> Result<()> {
let raw = load("config.toml")?;
let n = parse_number(&raw)?;
println!("{n}");
Ok(())
}downcast 的使用
fn inspect_error() -> Result<()> {
// unwrap_err 如果是 Err(e),返回其中的错误值;如果是 Ok,就 panic
let err = load("missing.txt").unwrap_err();
if let Ok(io) = err.downcast::<std::io::Error>() {
eprintln!("io error: {io}");
}
Ok(())
}thiserror
当需要向外暴露稳定的错误类型时,可以使用 thiserror,该工具是自定义错误枚举的派生工具。它会派生 Error + Display,并生成常用的 From 转换,保持错误类型清晰可控
用法
- 整个枚举通过
#[derive(Error)]来派生std::error::Error - 枚举项上方通过
#[error("...")]定义 Display - 在枚举项后通过括号或花括号包裹,通过
#[from]自动生成From<字段类型>,方便用?传播并建立错误链 - 需要显式指定来源时可用
#[source],附加调试信息可用#[backtrace]
use thiserror::Error;
use std::backtrace::Backtrace;
#[derive(Debug, Error)]
pub enum MyError {
// 匹配到 std::io::Error 时会通过 #[from] 自动变成 MyError::Io(...)
// #[error("io error: {0}")]} 会把内部的 io::Error 用它的 Display 展开
// 最终显示类似 io error: No such file or directory
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("Parser Error: {source}")]
Parser {
#[from]
#[source]
source: std::num::ParseFloatError,
#[backtrace]
bt: Backtrace,
},
#[error("invalid field: {field}")]
Invalid { field: String },
}
type Result<T> = std::result::Result<T, MyError>;注
- 括号
()的变体是元组风格,字段按位置访问,#[error("...{0}...")]用数字占位引用 - 花括号
{}的变体是结构体风格,字段有名字,#[error("...{field}...")]用字段名占位
选择哪种只影响你在代码和错误信息里如何引用字段,不影响匹配方式。
SNAFU
SNAFU 同样用于自定义错误类型,更强调显式上下文与定位信息,提供 context 和 with_context 辅助宏,使用方式与 thiserror 类似
重要
在调用 context 时需要引入派生自动生成的 "context selector",默认为 <变体名>Snafu,用它把上游错误包装成对应的变体
error.rs
use snafu::prelude::*;
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum MyError {
#[snafu(display("io error {source}"))]
IO { source: std::io::Error },
#[snafu(display("invalid id: {id}"))]
Invalid { id: u64 },
}main.rs
mod error;
use snafu::ResultExt;
use std::fs;
use crate::error::{MyError, IOSnafu};
fn main() -> Result<(), MyError> {
// context 需要派生出的 IOSnafu 选择器来把 io::Error 包装成 MyError::IO
let _file_open = fs::File::open("none-exit.txt").context(IOSnafu)?;
Ok(())
}