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
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()?))
}
}
由于 P1
和 P2
可能使用完全不相关的错误类型,我们不知道应该使用哪种错误类型。我们可以使用由 error_chain 定义的“可链接”错误类型,但这样我们就会回到使用“胖”结果的境地,即使 P1
和 P2
都是可靠的。
解决方案
此软件包提供了解决上述问题的方案。思路是
- 为无故障实现提供零成本的错误处理,允许它们使用
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>
是一个特质,让编译器推断正确的错误类型转换。宏提供了实现,以便never
和never
转换为never
,并且任何其他never
和MyError
的组合都转换为MyError
。CoercedError<E1, E2>
是一个使用CoercibleWith
确定适当的转换错误类型的类型别名。CoercedResult<T, E1, E2>
是Result<T, CoercedError<E1, E2>>
的快捷方式。
关于 never
由于 never
类型目前不稳定,此软件包实际上定义了自己的版本,称为 coercible_errors::Never
。一旦 never
变得稳定,coercible_errors::Never
将成为一个简单的别名到 never
,避免破坏性更改。
许可协议
(与 GNU LGPL 兼容)
依赖关系
~2.5–3.5MB
~72K SLoC