5 个版本

0.1.4 2019 年 1 月 23 日
0.1.3 2019 年 1 月 22 日
0.1.2 2019 年 1 月 22 日
0.1.1 2019 年 1 月 21 日
0.1.0 2019 年 1 月 21 日

#1252 in Rust 模式

50 每月下载量
用于 tree-buf

CECILL-C

18KB
203

可强制转换的错误

针对泛型特质的零成本错误处理。

理由

假设我们想要构建一个仓库,定义一个泛型特质,供其他人实现。以下是一个这样的特质的简化示例

    pub trait Producer {
        fn produce(&self) -> u16;
    }

某些实现可能与此定义配合良好,但其他实现可能在某些情况下遇到错误(例如,基于文件系统的实现可能会遇到 IOError)。我们称前者为 不可失败实现,后者为 可失败实现

为了支持这两种类型的实现,我们的特质方法应返回 Result<_, _>。这引发了结果应包含哪种错误类型的问题。

一个选择是为我们的仓库定义一个专门的错误类型,并强制实现者将他们的错误包装到这个类型中。

    pub trait Producer {
        fn produce(&self) -> Result<u16, MyError>;
    }

这可以工作,但它破坏了“零成本抽象”口号中的不可失败实现。实际上,Result<T, MyError> 可能比单独的 T 类型大得多。例如,使用 error_chain 包定义的简单 MyError 类型

  • <Result<(), MyError> 是 56 字节长(与 () 的 0 字节相比),
  • <Result<u16, MyError> 是 64 字节长(与 u16 的 2 字节相比)。

一个更灵活的选择是让实现者指定他们自己的错误类型

    pub trait Producer {
        type Error: Error + Send + 'static;
        fn produce(&self) -> Result<u16, Self::Error>;
    }

对于无故障实现,关联的 Error 类型可以被设置为 never 或任何其他无值类型(通常是空枚举)。编译器将优化掉这个错误类型,从而只返回成功类型。

我们现在有一个真正的零成本抽象,无故障实现不需要支付错误处理的费用。另一方面,处理我们特质的几个异构实现会更困难。例如,考虑以下类型

    pub struct PMax<P1, P2> (P1, P2);

    impl<P1: Producer, P2: Producer> Producer for PMax<P1, P2> {
        type Error = ???; // <<<< we have a problem here
        fn produce(&self) -> Result<u16, Self::Error> { 
            Ok(self.0.produce()?.max(self.1.produce()?))
        }

    }

由于 P1P2 可能使用完全不相关的错误类型,我们不知道应该使用哪种错误类型。我们可以使用由 error_chain 定义的“可链接”错误类型,但这样我们就会回到使用“胖”结果的境地,即使 P1P2 都是可靠的。

解决方案

此软件包提供了解决上述问题的方案。思路是

  • 为无故障实现提供零成本的错误处理,允许它们使用 never 作为它们的错误类型;
  • 通过要求它们使用特质设计者定义的专用错误类型,限制不可靠实现之间的异构性;
  • 在组合几个实现时让编译器推断最佳错误类型。

上面的例子将变为

    /// a dedicated error type
    pub struct MyError { /* ... */ }

    // define appropriate types and traits
    coercible_errors!(MyError);

    pub trait Producer {
        // require that Producer::Error be either MyError or never
        type Error: CoercibleWith<MyError> + CoercibleWith<Never>;
        fn produce(&self) -> Result<u16, Self::Error>;
    }

    pub struct PMax<P1, P2> (P1, P2);
    impl<P1: Producer, P2: Producer> Producer for PMax<P1, P2> 
        // this trait bound is required to be able to use CoercedError below
        where P1::Error: CoercibleWith<P2::Error>
    {
        // compute the most appropriate Error type based on P1 and P2;
        // especially, if P1 and P2 are both infallible,
        // PMax will be infallible as well.
        type Error = CoercedError<P1::Error, P2::Error>;
        fn produce(&self) -> Result<u16, Self::Error> {
            Ok(
              // the coerced error always implements From<_>
              // for both P1::Error and P2::Error,
              // so inner errors can simply be lifted with '?'
              self.0.produce()?
              .max(self.1.produce()?)
            )
        }

    }

coercible_errors 宏负责定义以下特性和类型

  • CoercibleWith<E> 是一个特质,让编译器推断正确的错误类型转换。宏提供了实现,以便 nevernever 转换为 never,并且任何其他 neverMyError 的组合都转换为 MyError
  • CoercedError<E1, E2> 是一个使用 CoercibleWith 确定适当的转换错误类型的类型别名。
  • CoercedResult<T, E1, E2>Result<T, CoercedError<E1, E2>> 的快捷方式。

关于 never

由于 never 类型目前不稳定,此软件包实际上定义了自己的版本,称为 coercible_errors::Never。一旦 never 变得稳定,coercible_errors::Never 将成为一个简单的别名到 never,避免破坏性更改。

许可协议

CECILL-C

(与 GNU LGPL 兼容)

依赖关系

~2.5–3.5MB
~72K SLoC