2 个版本
0.0.2 | 2021年2月28日 |
---|---|
0.0.1 | 2020年11月15日 |
#1379 in 开发工具
18KB
129 行
概述 & 动机
在 Rust 中,处理错误的标准方式是使用像 Anyhow 这样的 crate 将任何错误简单地向上传递给调用者(并且希望最终传递给能够处理它的代码),通过 ?
操作符。或者为每个模块或 crate 创建一个复杂的自定义错误类型。这种错误类型通常采用枚举的形式,每个变体对应于从该 crate 或模块可能发生的每种错误。
这些方法的问题在于,你的函数的使用者可能不知道每个函数可能发生哪些错误。在 Anyhow 的情况下,这个问题更加严重,因为返回 Anyhow 错误类型的函数可以返回 真正意义上的任何错误。在某些情况下这可能可以接受,但这显然不是理想的。当你创建自定义错误类型时,有时这不是一件简单的事情,这可能是大多数 crate 没有为每个函数提供错误类型的原因。将这种错误处理方式与 Java 伪代码中的方式进行比较
public Object readAndParseFile(Path file) throws IOException, ParseError{
String contents = Files.read(file); //throws IOException
Object parsedContents = parseString(contents); //throws ParseError
return parsedContents;
}
在这个例子中,可能出现的失败模式在函数的签名中得到了明确的文档记录。此外,Javadoc 注释通常描述在哪些场景下会抛出每个异常。你可以在这里了解更多关于我对 Rust 错误处理系统与 Java 相比的看法 here。
Polyerror通过使其变得如此简单(实际上是一个单行宏调用)来定义一个舒适(?工作)且正确的错误类型,以至于为每个函数有一个单独的错误类型是实用的。这样,对于最终用户来说,总是很明显函数可能会以哪种方式出错。请参阅示例部分以获取更多详细信息并了解如何使用此crate。此crate的另一个优点是它非常简单易用——一旦您阅读了本文档,您就了解了有关crate的所有信息,与Rust的其他错误库相比。仅导出了一个宏,它提供了您在Rust中进行健壮、易于理解(对您和您的用户而言)且正确的错误处理所需的一切。
示例
宏会展开成什么样子?
考虑以下Rust代码(基于此crate的基本_use测试)
use std::str::ParseBoolError;
use std::num::ParseIntError;
create_error!(pub ParseThenCombineError: ParseBoolError, ParseIntError);
pub fn parse_then_combine(a: &str, b: &str) -> Result<String, ParseThenCombineError> {
let parsed_bool: bool = a.parse()?;
let parsed_int: i32 = b.parse()?;
Ok(format!("{} {}", parsed_bool, parsed_int))
}
在这个玩具函数中,有两个可能的错误:ParseBoolError和ParseIntError(都来自标准库)。在使用传统模式的情况下,您可能需要使用类似Anyhow的东西,这样就会从您的crate的用户那里隐藏有用的(如果不是至关重要的)信息,迫使他们深入研究您的代码或希望它得到了适当的文档(这让我想起了C和C++中如何指定生命周期),或者简单地将这些两个错误类型添加到一个全局错误枚举中。在这里,我们将仅使用ParseThenCombineError
(如果您的风格偏好,您可以自由选择更简洁的名称)来处理parse_then_combine
函数。由于它是在函数之前单行定义的,因此这并不会带来任何显著的繁琐或生产力损失。
为了准确了解正在发生的事情,上面的create_error!
调用会展开成以下源代码
#[derive(Debug)]
pub enum ParseThenCombineError {
ParseBoolError(ParseBoolError),
ParseIntError(ParseIntError),
}
impl ::std::fmt::Display for ParseThenCombineError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl ::std::error::Error for ParseThenCombineError {}
impl ::std::convert::From<ParseBoolError> for ParseThenCombineError {
fn from(error: ParseBoolError) -> Self {
Self::ParseBoolError(error)
}
}
impl ::std::convert::From<ParseIntError> for ParseThenCombineError {
fn from(error: ParseIntError) -> Self {
Self::ParseIntError(error)
}
}
注意
create_error!
生成的Rust展开代码将精确地引用您指定的各种错误类型。例如,如果提供了完整的路径(例如std::num::ParseIntError
),那么产生的Rust代码也会使用该完整路径。此外,变体的名称将是StdNumParseIntError
。此外,如果名称中有下划线,例如actix_web::Error
,那么这些下划线将被适当地转换成枚举变体名称:ActixWebError
。- 您可以使用任何有效的访问修饰符,包括无(通常用于私有继承),pub(crate),等等。
- 创建的错误类型在docs.rs中进行了文档说明,就像任何手动创建的错误类型一样,使其使用变得简单。
- 请注意,
parse_then_combine
的返回类型不是一个类型别名。当使用此crate时,这更可取,因为每个错误类型只使用一次。
待办事项
基本功能- 允许从函数返回错误,这些错误不是简单上游错误的包装(欢迎PR,但请先提出一个问题来讨论语法)。在此期间,您可以创建一个更传统的错误类型,用于那些不是简单包装上游错误的错误。
- 在构建时包含回溯(最好等到Rust标准库中的回溯稳定,尽管有一个crate在理论上可以在此期间处理它)
依赖项
~1.5MB
~37K SLoC