anyhow crate 提供了三个抽象:
struct anyhow::Error
struct anyhow::Result<T>
:等效于std::result::Result<T, anyhow::Error>
;trait anyhow::Context
:自动为 Result/Option 实现该 trait;
struct anyhow::Error 类型:
- anyhow::Error 实现了 Send+Sync+‘static, 可用于多线程环境中;
- anyhow::Error 支持 backtrace (需要运行时配置
RUST_BACKTRACE=1
环境变量); - anyhow::Error 只占用一个 usize 大小, 而 Box trait object 占用两个 usize;
andyhow::Error
可以转换为 Box<dyn StdError>:
- From<Error> for Box<dyn StdError + ‘static>
- From<Error> for Box<dyn StdError + Send + ‘static>
- From<Error> for Box<dyn StdError + Send + Sync + ‘static>:
反过来,任何实现了 std::error::Error trait 的错误 都可以转换为 anyhow::Error
:
- From<E> where E: StdError + Send + Sync + ‘static
anyhow::Error
实现了 Deref<Target = dyn std::error::Error + Sync + Send> ,可以作为 std::error::Error 来使用。
anyhow::Error 方法:
- new(error) 函数: 从标准库 error 创建 anyhow::Error
- msg(message) 函数: 从 message 创建 anyhow::Error,message 一般是字符串,常用在 Result/Option 的 .map_err(anyhow::Error::msg) 方法中来转换错误类型。
- context(context) 方法: 为 anyhow::Error 对象添加上下文信息, 后续打印错误时,
先打印 context 再打印底层 error 信息
;
impl Error
// new 将 std Error 的错误转换为 anyhow::Error
pub fn new<E>(error: E) -> Self where E: StdError + Send + Sync + 'static
// 从 message 创建 anyhow::Error
pub fn msg<M>(message: M) -> Self where M: Display + Debug + Send + Sync + 'static
use anyhow::{Error, Result};
use futures::stream::{Stream, StreamExt, TryStreamExt};
async fn demo<S>(stream: S) -> Result<Vec<Output>> where S: Stream<Item = Input>
{
stream
.then(ffi::do_some_work) // 返回 Result<Output, &str>
.map_err(Error::msg) // 将 Err(&str) 转换为 anyhow::Error
// 返回 Result,如果 collect 过程中出错,则返回 Err(anyhow::Error)
.try_collect()
.await
}
// 为 anyhow::Error 对象添加新的 context,一般传入的是字符串或错误值,
// 消耗自身,返回一个新的错误对象。
pub fn context<C>(self, context: C) -> Self
where C: Display + Send + Sync + 'static
use anyhow::Result;
use std::fs::File;
use std::path::Path;
struct ParseError {
line: usize,
column: usize,
}
fn parse_impl(file: File) -> Result<T, ParseError> {
//...
}
pub fn parse(path: impl AsRef<Path>) -> Result<T> {
let file = File::open(&path)?;
parse_impl(file).map_err(|error| {
let context = format!( "only the first {} lines of {} are valid",
error.line, path.as_ref().display(), );
anyhow::Error::new(error).context(context)
})
}
// 获得 Error 相关的 backtrace
pub fn backtrace(&self) -> &Backtrace
// An iterator of the chain of source errors contained by this Error.
// This iterator will visit every error in the cause chain of this error
// object, beginning with the error that this error object was created from.
pub fn chain(&self) -> Chain<'_>
use anyhow::Error;
use std::io;
pub fn underlying_io_error_kind(error: &Error) -> Option<io::ErrorKind> {
for cause in error.chain() {
if let Some(io_error) = cause.downcast_ref::<io::Error>() {
return Some(io_error.kind());
}
}
None
}
pub fn root_cause(&self) -> &(dyn StdError + 'static)
// Returns true if E is the type held by this error object.
pub fn is<E>(&self) -> bool where E: Display + Debug + Send + Sync + 'static,
// Attempt to downcast the error object to a concrete type.
pub fn downcast<E>(self) -> Result<E, Self>
where E: Display + Debug + Send + Sync + 'static,
// Downcast this error object by reference.
pub fn downcast_ref<E>(&self) -> Option<&E>
where E: Display + Debug + Send + Sync + 'static,
// If the error was caused by redaction, then return a tombstone instead of
// the content.
match root_cause.downcast_ref::<DataStoreError>() {
Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)),
None => Err(error),
}
anyhow::Error 实现了 std::fmt 的 Debug 和 Display trait,使用
{}/{:#}/{:?}/{:#?}
打印错误信息:
-
{}
:只显示最外层的错误,如:Failed to read instrs from ./path/to/instrs.json -
{:#}
: 显示所有错误,如: Failed to read instrs from ./path/to/instrs.json: No such file or directory (os error 2) -
{:?}
显示 backtraceError: Failed to read instrs from ./path/to/instrs.json Caused by: No such file or directory (os error 2) // and if there is a backtrace available: Error: Failed to read instrs from ./path/to/instrs.json Caused by: No such file or directory (os error 2) Stack backtrace: 0: <E as anyhow::context::ext::StdError>::ext_context at /git/anyhow/src/backtrace.rs:26 1: core::result::Result<T,E>::map_err at /git/rustc/src/libcore/result.rs:596 2: anyhow::context::<impl anyhow::Context<T,E> for core::result::Result<T,E>>::with_context at /git/anyhow/src/context.rs:58 3: testing::main at src/main.rs:5 4: std::rt::lang_start at /git/rustc/src/libstd/rt.rs:61 5: main 6: __libc_start_main 7: _start
-
{:#?}
: 使用 struct 风格的错误表示:Error { context: "Failed to read instrs from ./path/to/instrs.json", source: Os { code: 2, kind: NotFound, message: "No such file or directory", }, }
Rust >= 1.65 支持捕获 backtrace, 需要启用 std::backtrace
定义如下环境变量:
- panic 和 error 都包含 backtrace:
RUST_BACKTRACE=1
- 只对 error 捕获 backtrace:
RUST_LIB_BACKTRACE=1
; - 只对 panic 捕获 backtrace:
RUST_BACKTRACE=1 和 RUST_LIB_BACKTRACE=0
.
anyhow::Result<T>
是 std::result::Result<T, anyhow::Error>
类型别名,它使用
anyhow::Error, 在显示出错信息时包含 context 和 backtrace:
use anyhow::{Context, Result};
// 使用 anyhow::Result<()> 作为函数返回值, 内部 Err 类型是 anyhow::Error
fn main() -> Result<()> {
//...
it.detach().context("Failed to detach the important thing")?;
let content = std::fs::read(path).with_context(||
format!("Failed to read instrs from {}", path))?;
//...
// 其它实现标准库 Error 的类型会被自动转换为 anyhow::Error
let config = std::fs::read_to_string("cluster.json")?;
let map: ClusterMap = serde_json::from_str(&config)?;
println!("cluster info: {:#?}", map);
Ok(())
}
Trait anyhow::Context
为 Result/Option 提供了 context()/with_context() 方法
,转移对象所有权,返回 anyhow::Result
对象:
- Result/Option 都实现了 anyhow::Context trait;
- 任何实现 std::error::Error 的类型都添加 context()/with_context() 方法, 所以 thiserror 包中通过宏来自定义的 error 类型都可以和 anyhow::Context 使用;
后续报错时, 会先返回 context()/with_context() 的上下文信息, 然后是 Caused by 开始的未加 context 前的错误.
pub trait Context<T, E>: Sealed {
// 两个方法都会转移对象所有权,返回 any::Result 类型对象。
// Required methods
fn context<C>(self, context: C) -> Result<T, Error>
where C: Display + Send + Sync + 'static;
fn with_context<C, F>(self, f: F) -> Result<T, Error>
where C: Display + Send + Sync + 'static, F: FnOnce() -> C;
}
// 示例:
use anyhow::{Context, Result};
use std::fs;
use std::path::PathBuf;
pub struct ImportantThing {
path: PathBuf,
}
impl ImportantThing {
pub fn detach(&mut self) -> Result<()> {...}
}
pub fn do_it(mut it: ImportantThing) -> Result<Vec<u8>> {
// 为 Result 自动添加 context() 方法,返回 anyhow::Result<T> 类型对象
it.detach().context("Failed to detach the important thing")?;
let path = &it.path;
// with_context() 方法使用一个闭包函数返回可显示的字符串,
// 该方法返回 anyhow::Result<T> 类型对象
let content = fs::read(path)
.with_context(|| format!("Failed to read instrs from {}", path.display()))?;
Ok(content)
}
// 后续打印 anyhow::Error 对象时, 会先返回 context()/with_context() 的内容,
// 然后是 Caused by 开始的未加 context 前的错误信息。
// Error: Failed to read instrs from ./path/to/instrs.json // context() 信息
// Caused by:
// No such file or directory (os error 2) // fs::read() 的报错信息
anyhow::anyhow!/bail!/ensure!()
宏:
- anyhow!() :从字符串或非 anyhow::Error 类型对象来创建 anyhow::Error 对象;
- 常见场景:使用 thiserror::Error derive macro 实现的自定义错误类型对象;
- 创建一个 anyhow::Error 类型对象,但是函数不返回。
- bail!() :创建
并返回
anyhow::Result 类型对象,等效于return Err(anyhow!($args...))
- ensure!(): 创建
并返回
anyhow::Result 对象,等效于if !$cond { return Err(anyhow!($args...)); }
这些宏函数的参数有三种类型(可以使用任意一种):
- 格式化字符串字面量;
- 实现 Display + Debug 的任意其它类型;
- 实现 std::error::Error + Display + Debug 的任意其它类型,如 thiserror::Error;
对于第三种类型,传入的 error 将作为返回的 anyhow::Error 的 source() 方法返回。
macro_rules! anyhow {
($msg:literal $(,)?) => { ... };
($err:expr $(,)?) => { ... };
($fmt:expr, $($arg:tt)*) => { ... };
}
use anyhow::{bail, Context, Result};
use std::fs;
use std::io::Read;
use thiserror::Error;
// 为自定义类型实现 thiserror::Error
#[derive(Clone, Debug, Eq, Error, PartialEq)]
// thiserror::Error drive macro 关联的 attribute macro
#[error("Found no username in {0}")]
struct EmptyUsernameError(String);
// 返回 anyhow::Result, 内部使用 anyhow::Error
fn read_username(path: &str) -> Result<String> {
let mut username = String::with_capacity(100);
fs::File::open(path)
.with_context(|| format!("Failed to open {path}"))?
.read_to_string(&mut username)
.context("Failed to read")?; // anyhow::Context
if username.is_empty() {
bail!(EmptyUsernameError(path.to_string()));
}
Ok(username)
}
fn main() {
//fs::write("config.dat", "").unwrap();
match read_username("config.dat") {
Ok(username) => println!("Username: {username}"),
// 使用 anyhow::Error 自定义的 Debug 格式化输出.
Err(err) => println!("Error: {err :?}"),
}
}
use anyhow::{anyhow, Result};
fn lookup(key: &str) -> Result<V> {
if key.len() != 16 {
return Err(anyhow!("key length must be 16 characters, got {:?}", key));
}
// ...
}
if !has_permission(user, resource) {
// 格式1: 格式化字符串
bail!("permission denied for accessing {}", resource);
}
#[derive(Error, Debug)]
enum ScienceError {
#[error("recursion limit exceeded")]
RecursionLimitExceeded,
...
}
if depth > MAX_DEPTH {
// 格式2: 实现 std::error:Error 的对象
bail!(ScienceError::RecursionLimitExceeded);
}
// ensure!() 宏当 condition 不满足时,返回 anyhow::Result Error
ensure!(user == 0, "only user 0 is allowed");
#[derive(Error, Debug)]
enum ScienceError {
#[error("recursion limit exceeded")]
RecursionLimitExceeded,
...
}
ensure!(depth <= MAX_DEPTH, ScienceError::RecursionLimitExceeded);