跳过正文

anyhow

··1962 字
Rust Rust-Crate
rust crate - 这篇文章属于一个选集。
§ 6: 本文

anyhow crate 提供了三个抽象:

  1. struct anyhow::Error
  2. struct anyhow::Result<T> :等效于 std::result::Result<T, anyhow::Error> ;
  3. trait anyhow::ContextResult/Option 实现该 trait;

struct anyhow::Error 类型:

  1. anyhow::Error 实现了 Send+Sync+‘static, 可用于多线程环境中;
  2. anyhow::Error 支持 backtrace (需要运行时配置 RUST_BACKTRACE=1 环境变量);
  3. 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>:

anyhow::Error 实现了 Deref<Target = dyn std::error::Error + Sync + Send> ,可以作为 std::error::Error 来使用。

反过来,实现 std::error::Error trait 的错误类型值可以转换为 anyhow::Error:

  • From<E> where E: StdError + Send + Sync + ‘static

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 信息= ;
// 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) -> std::result::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, 受 RUST_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~,使用 ~{}/{:#}/{:?}/{:#?} 打印错误信息:

  1. {} :只显示最外层的错误,如:Failed to read instrs from ./path/to/instrs.json
  2. {:#} : 显示所有错误,如:Failed to read instrs from ./path/to/instrs.json: No such file or directory (os error 2)
  3. {:?} : 显示 backtrace(受环境变量控制),同时也是 main 函数返回 anyhow::Result 时的打印形式。
  4. {:#?} : 使用 struct 风格的错误表示:
// 1. {} 或 .to_string() ,只打印 context() 或最外层错误
Failed to read instrs from ./path/to/instrs.json

// 2. {:#}:同时打印 context() 和内部错误
Failed to read instrs from ./path/to/instrs.json: No such file or directory (os error 2)

// 3. {:?}: 打印 context()、内部错误和 backtrace(如果开启了环境变量),同时这也是 main 函数返回 anyhow::Result 时的打印形式。
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

// 4. {:#?} 使用 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 定义的如下环境变量:

  1. panic 和 error 都包含 backtrace: RUST_BACKTRACE=1
  2. 只对 error 捕获 backtrace: RUST_LIB_BACKTRACE=1;
  3. 只对 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};

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 对象。

  • with_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>> {
    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 实现的自定义错误类型对象;
  • bail!() :创建 并返回 anyhow::Result 类型对象,等效于 return Err(anyhow!($args...))
  • ensure!(): 创建 并返回 anyhow::Result 对象,等效于 if !$cond { return Err(anyhow!($args...)); }

这些宏函数的参数有三种类型(可以使用任意一种):

  1. 格式化字符串字面量;
  2. 实现 Display + Debug 的任意其它类型;
  3. 实现 std::error::Error + Display + Debug 的任意其它类型,如 thiserror::Error;

对于第三种类型,传入的 error 将作为返回的 anyhow::Error 的 source() 方法返回。

macro_rules! anyhow {
    ($msg:literal $(,)?) => { ... };  // 字符串字面量
    ($err:expr $(,)?) => { ... };     // 实现 std::error::Error + Display + Debug 的任意其它类型,如 thiserror::Error;
    ($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)]
#[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")?;

    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);
rust crate - 这篇文章属于一个选集。
§ 6: 本文

相关文章

bytes
··2922 字
Rust Rust-Crate
bytes 提供了高效的 zero-copy 连续内存区域的共享和读写能力。
chrono
··4023 字
Rust Rust-Crate
chrono 提供了丰富的 Date/Time 类型和相关操作。
hyper
··861 字
Rust Rust-Crate
hyper 是高性能的异步 HTTP 1/2 底层库。
serde
··7077 字
Rust Rust-Crate
Rust 主流的序列化/反序列化库。