error-handling - 源错误是否应该在显示输出中包含该源?

标签 error-handling rust

我有一个错误类型,它包含 Error 特征,它包装了一个潜在的错误原因,所以 source 方法返回 Some(source) 。我想知道我的错误类型上的 Display impl 是否应该包含对该源错误的描述。
我可以看到两个选项:

  • 是的,在 source 输出中包含 Display,例如“打开数据库时出错:没有这样的文件”

  • 这使得仅通过使用 "{}" 格式化就可以轻松打印整个错误链,但不可能只显示错误本身而没有底层的源错误链。此外,它使 source 方法有点毫无意义,并且让客户端代码无法选择如何格式化链中每个错误之间的格式。尽管如此,在我发现的示例代码中,这种选择似乎很常见。
  • 不,只需打印错误本身,例如“打开数据库时出错”并将其留给客户端代码遍历并显示 source 如果它想将其包含在输出中。

  • 这使客户端代码可以选择是只显示表面错误还是整个链,以及在后一种情况下如何格式化链中每个错误之间的分隔。它让客户端代码承担了遍历链的负担,我还没有使用规范实用程序来方便地从错误中格式化错误链,每个错误都只有 Display 本身,不包括 source 。 (所以我当然有自己的。)
    snafu crate(我真的很喜欢)似乎暗示支持选项 2,因为带有 source 字段但没有 display 属性的错误变体默认格式化不包含 Displaysource 输出。
    也许我真正的问题是:source 方法的目的是什么?是为了让格式化错误链更加灵活吗?或者 Display 真的应该输出关于错误的所有用户可见的内容,而 source 只是为了开发人员可见的目的?
    我很想看到一些明确的指导,最好是在 Error 特性的文档中。
    #[derive(Debug)]
    enum DatabaseError {
        Opening { source: io::Error },
    }
    
    impl Error for DatabaseError {
        fn source(&self) -> Option<&(dyn Error + 'static)> {
            match self {
                DataBaseError::Opening { source } => Some(source),
            }
        }
    }
    
    impl fmt::Display for DatabaseError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            match self {
                DatabaseError::Opening { source } => {
                    // ??? Should we include the source?
                    write!(f, "Error opening database: {}", source)
    
                    // ??? Or should we leave it to the caller to call .source()
                    //     if they want to include that in the error description?
                    write!(f, "Error opening database")
                }
            }
        }
    }
    

    最佳答案

    是否在 Display 实现上打印源错误的两个选项创建了两种设计流派。
    目前,尽管存在对这两种方式的看法,但两者都没有比另一个更惯用,并且最终可以在 future 找到社区范围的共识,或者在特定项目或代码库的上下文中可能已经如此。
    除了 C-GOOD-ERR 之外,Rust API 指南没有对错误中的 Display 提出意见,该指南仅指出错误类型的 Display 消息应该是“小写,没有尾随标点符号,并且通常简洁”。有一个 pending proposal 来更新这个指南,指示开发人员在他们的 source impl 中排除 Display。同样,如果没有一些摩擦,该提案就不会存在。
    在这里和现在可以做的是客观地陈述两者之间的主要区别,同时澄清一些可能的误解。
    1. 是的,在你的 source impl 中包含 DisplaySNAFU 示例:

    #[derive(Debug, Snafu)]
    enum Error {
        #[snafu(display("Could not read data set token: {}", source))]
        ReadToken {
            #[snafu(backtrace)]
            source: ReadDataSetError,
        },
    }
    
    正如问题中已经提到的,关键优势是提供全部信息就像打印错误值一样简单。
    eprintln!("[ERROR] {}", err);
    
    它简单易行,不需要帮助函数来报告错误,尽管缺乏演示灵活性。如果没有字符串操作,您将得到一连串以冒号分隔的错误。
    [ERROR] Could not read data set token: Could not read item value: Undefined value length of element tagged (5533,5533) at position 3548
    
    2. 不,在你的 source impl 上省略 Display
    #[derive(Debug, Snafu)]
    enum Error {
        #[snafu(display("Could not read data set token"))]
        ReadToken {
            #[snafu(backtrace)]
            source: ReadDataSetError,
        },
    }
    
    虽然这不会像以前那样通过单行打印为您提供完整信息,但您可以将该任务留给项目范围的错误报告者。这也为 API 的使用者提供了更大的错误呈现灵活性。
    下面是一个简单的例子。显示错误的回溯需要额外的逻辑。
    fn report<E: 'static>(err: E)
    where
        E: std::error::Error,
        E: Send + Sync,
    {
        eprintln!("[ERROR] {}", err);
        if let Some(cause) = err.source() {
            eprintln!();
            eprintln!("Caused by:");
            for (i, e) in std::iter::successors(Some(cause), |e| e.source()).enumerate() {
                eprintln!("   {}: {}", i, e);
            }
        }
    }
    
    还值得考虑与固执己见的库集成的兴趣。也就是说,生态系统中的某些 crate 可能已经对选择哪个选项做出了假设。在 anyhow 中,错误报告默认已经遍历错误的源链。当使用 anyhow 进行错误报告时,你不应该添加 source ,否则你可能会想出一个令人讨厌的重复消息列表:
    [ERROR] Could not read data set token: Could not read item value: Undefined value length of element tagged (5533,5533) at position 3548
    
    Caused by:
       0: Could not read item value: Undefined value length of element tagged (5533,5533) at position 3548
       1: Undefined value length of element tagged (5533,5533) at position 3548
    
    同样, eyre 库提供了一个可定制的错误报告抽象,但是 eyre crate 生态系统中现有的错误报告器也假设源不是由错误的 Display 实现打印的。
    在任一情况下...
    这个决定只在错误报告中起作用,在错误匹配或以其他方式处理错误时不起作用。 source 方法的存在为所有错误类型建立了一个链状结构,可以在程序的模式匹配和后续流程控制中加以利用。Error::source 方法在生态系统中是有目的的,不管错误是如何报告的。
    也可以看看:
  • 错误处理保护组 ( GitHub ),有朝一日可能会就此主题建立其他约定和指南。
  • 关于error-handling - 源错误是否应该在显示输出中包含该源?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62869360/

    相关文章:

    angular - 错误处理Angular 6

    json - 区分JSON和其他错误

    rust - 清除前设置剪刀矩形

    rust - 从 HashMap 可变借用和生命周期省略

    tuples - 元组 splat/apply in Rust

    swift - “ fatal error :在展开可选值时意外发现nil”是什么意思?

    REST API 处理错误的最佳实践

    php - 为什么我的PHP错误没有显示?

    machine-learning - 如何保存在SmartCore中训练的模型?

    rust - 为什么 Rust 需要知道模块中的代码属于谁?