使用泛型类型时,如何实现“From”的冲突?

时间:2016-05-20 12:54:41

标签: rust

我试图实现一个错误枚举,它可能包含与我们的某个特征相关的错误,如下所示:

trait Storage {
    type Error;
}

enum MyError<S: Storage> {
    StorageProblem(S::Error),
}

我还尝试实施From特征,以允许从MyError的实例构建Storage::Error

impl<S: Storage> From<S::Error> for MyError<S> {
    fn from(error: S::Error) -> MyError<S> {
        MyError::StorageProblem(error)
    }
}

playground

然而,这无法编译:

error[E0119]: conflicting implementations of trait `std::convert::From<MyError<_>>` for type `MyError<_>`:
  --> src/main.rs:9:1
   |
9  | / impl<S: Storage> From<S::Error> for MyError<S> {
10 | |     fn from(error: S::Error) -> MyError<S> {
11 | |         MyError::StorageProblem(error)
12 | |     }
13 | | }
   | |_^
   |
   = note: conflicting implementation in crate `core`

我不明白为什么编译器认为这已经实现了。错误消息告诉我,已经有From<MyError<_>>的实现(有),但我没有尝试在此实现 - 我试图实现{{我可以看到1}}和From<S::Error>MyError的类型不同。

我是否遗漏了仿制药的基本内容?

2 个答案:

答案 0 :(得分:9)

此处的问题是有人可能会实施Storage,以便您编写的From impl与impl<T> From<T> for T标准库中的impl重叠(也就是说,任何内容都可以转换为本身)。

具体地,

struct Tricky;

impl Storage for Tricky {
    type Error = MyError<Tricky>;
}

(这里的设置意味着这并没有实际编译 - MyError<Tricky>无限大 - 但该错误与关于impl s / coherence / overlap的推理无关,事实上MyError的小改动可以在不改变基本问题的情况下进行编译,例如添加Box StorageProblem(Box<S::Error>),

如果我们在你的impl中用Tricky代替S,我们会得到:

impl From<MyError<Tricky>> for MyError<Tricky> {
    ...
}

implT == MyError<Tricky>的自转换完全匹配,因此编译器无法知道选择哪一个。 Rust编译器避免了这样的情况,而不是做出任意/随机的选择,因此必须拒绝原始代码。

这种一致性限制肯定会令人讨厌,这是specialisation备受期待的特性的原因之一:本质上允许手动指示编译器如何处理重叠...至少one of the extensions以当前的限制形式允许。

答案 1 :(得分:1)

一致性问题的解决方法是使用Result::map_err自行执行转换。然后,您可以使用Resulttry!

结束?
fn example<S: Storage>(s: S) -> Result<i32, MyError<S>> {
    s.do_a_thing().map_err(MyError::StorageProblem)?;
    Ok(42)
}

当存在具有相同基础Error的错误变体时,此解决方案也很有用,例如,如果要分离“文件打开”和“文件读取”错误,这两个错误都是{{1} }。