How is there a conflicting implementation of `From` when using a generic type?
A workaround for the coherence issue is to use some other method or trait.
In the specific posted example involving Result
s, you can use Result::map_err
to perform the conversion yourself. You can then use the transformed Result
with ?
:
fn example<S: Storage>(s: S) -> Result<i32, MyError<S>> {
s.do_a_thing().map_err(MyError::StorageProblem)?;
Ok(42)
}
This solution is also valuable when there are error variants that have the same underlying Error
, such as if you want to separate "file opening" and "file reading" errors, both of which are io::Error
.
In other cases, you might need to create a brand new method on your type or an alternate trait:
struct Wrapper<T>(T);
// Instead of this
//
// impl<T, U> From<Wrapper<T>> for Wrapper<U>
// where
// T: Into<U>,
// {
// fn from(other: Wrapper<T>) -> Self {
// Wrapper(other.0.into())
// }
// }
// Use an inherent method
impl<T> Wrapper<T> {
fn from_another<U>(other: Wrapper<U>) -> Self
where
U: Into<T>,
{
Wrapper(other.0.into())
}
}
// Or create your own trait
trait MyFrom<T> {
fn my_from(other: T) -> Self;
}
impl<T, U> MyFrom<Wrapper<T>> for Wrapper<U>
where
T: Into<U>,
{
fn my_from(other: Wrapper<T>) -> Self {
Wrapper(other.0.into())
}
}
The problem here is someone may implement Storage
so that the From
impl you have written overlaps with the impl in the standard library of impl<T> From<T> for T
(that is, anything can be converted to itself).
Specifically,
struct Tricky;
impl Storage for Tricky {
type Error = MyError<Tricky>;
}
(The set-up here means this doesn't actually compile—MyError<Tricky>
is infinitely large—but that error is unrelated to the reasoning about impl
s/coherence/overlap, and indeed small changes to MyError
can make it compile without changing the fundamental problem, e.g. adding a Box
like StorageProblem(Box<S::Error>),
.)
If we substitute Tricky
in place of S
in your impl, we get:
impl From<MyError<Tricky>> for MyError<Tricky> {
...
}
This impl
exactly matches the self-conversion one with T
== MyError<Tricky>
, and hence the compiler wouldn't know which one to choose. Instead of making an arbitrary/random choice, the Rust compiler avoids situations like this, and thus the original code must be rejected due to this risk.
This coherence restriction can definitely be annoying, and is one of reasons that specialisation is a much-anticipated feature: essentially allows manually instructing the compiler how to handle overlap... at least, one of the extensions to the current restricted form allows that.