在 Rust 中向上传播错误的详细信息

Propagating details of an error upwards in Rust

我想向上传播错误的详细信息。我以前使用过错误链,但据我所知,它没有得到维护或与生态系统的其余部分保持兼容。

比如本例中:

use std::str::FromStr;
use anyhow::Result;

fn fail() -> Result<u64> {
  Ok(u64::from_str("Some String")?)
}

fn main() {

  if let Err(e) = fail(){
    println!("{:?}", e);
  }

我得到的错误是:

invalid digit found in string

我需要错误消息来提供关键详细信息,包括故障点,例如:

- main: invalid digit found in string
- fail: "Some String" is not a valid digit

执行此操作的最佳方法是什么?

当您 运行 您的应用程序将环境变量 RUST_BACKTRACE 设置为 1full 时,您将获得更多错误详细信息,而无需重新编译你的程序。然而,这并不意味着您会收到像 "Some String" is not a valid digit 这样的额外消息,因为解析函数根本不会生成这样的消息。

这是一件很难完成的事情,我不确定是否有一种简单的 non-invasive 方法可以在不知道调用的特定函数的情况下捕获任何可能错误的所有详细信息。例如,我们可能希望显示失败的函数调用的一些参数,但评估其他参数可能会出现问题——它们甚至可能无法转换为字符串。

也许参数也是另一个函数调用,那么我们应该捕获它的参数还是只捕获它的 return 值?

我快速 this example 表明我们至少可以相当简单地捕获确切的源表达式。它提供了一个 detail_error! 宏,它接受一个产生 Result<T, E> 的表达式并发出一个产生 Result<T, DetailError<E>> 的表达式。 DetailError 包装了原始错误值,另外还包含对提供给宏的原始源代码字符串的引用。

use std::error::Error;
use std::str::FromStr;

#[derive(Debug)]
struct DetailError<T: Error> {
    expr: &'static str,
    cause: T,
}

impl<T: Error> DetailError<T> {
    pub fn new(expr: &'static str, cause: T) -> DetailError<T> {
        DetailError { expr, cause }
    }
    
    // Some getters we don't use in this example, but should be present to have
    // a complete API.
    #[allow(dead_code)]
    pub fn cause(&self) -> &T {
        &self.cause
    }
    
    #[allow(dead_code)]
    pub fn expr(&self) -> &'static str {
        self.expr
    }
}

impl<T: Error> Error for DetailError<T> { }

impl<T: Error> std::fmt::Display for DetailError<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        write!(f, "While evaluating ({}): ", self.expr)?;
        std::fmt::Display::fmt(&self.cause, f)
    }
}

macro_rules! detail_error {
    ($e:expr) => {
        ($e).map_err(|err| DetailError::new(stringify!($e), err))
    }
}

fn main() {
    match detail_error!(u64::from_str("Some String")) {
        Ok(_) => {},
        Err(e) => { println!("{}", e); }
    };
}

这会产生运行时输出:

While evaluating (u64::from_str("Some String")): invalid digit found in string

请注意,这仅显示字符串,因为它是源代码中的文字。如果您改为传递 variable/parameter,您将在错误消息中看到该标识符而不是字符串。

anyhow 为此提供了 context() and with_context() 方法:

use anyhow::{Context, Result};
use std::str::FromStr;

fn fail() -> Result<u64> {
    let s = "Some String";
    Ok(u64::from_str(s).with_context(|| format!("\"{s}\" is not a valid digit"))?)
}

fn main() {
    if let Err(e) = fail() {
        println!("{:?}", e);
    }
}
"Some String" is not a valid digit

Caused by:
    invalid digit found in string

如果要自定义格式,可以使用Error::chain()方法:

if let Err(e) = fail() {
    for err in e.chain() {
        println!("{err}");
    }
}
"Some String" is not a valid digit
invalid digit found in string

如果您需要更多详细信息(例如错误发生的位置),您可以使用自定义错误类型并将其向下转换(对于错误源,您还可以捕获回溯)。