I stumbled upon some surprising coherence errors when implementing Borrow
for an associated type. (This came up in the context of this question on Stack Overflow.)
The following code
use std::borrow::Borrow;
pub trait Wrap {
type Wrapped;
}
pub struct Wrapper<W: Wrap>(W::Wrapped);
impl<W, T> Borrow<T> for Wrapper<W>
where
W: Wrap<Wrapped = T>,
{
fn borrow(&self) -> &T {
&self.0
}
}
results in this error:
error[E0119]: conflicting implementations of trait `std::borrow::Borrow<Wrapper<_>>` for type `Wrapper<_>`:
--> src/lib.rs:9:1
|
9 | / impl<W, T> Borrow<T> for Wrapper<W>
10 | | where
11 | | W: Wrap<Wrapped = T>,
12 | | {
... |
15 | | }
16 | | }
| |_^
|
= note: conflicting implementation in crate `core`:
- impl<T> std::borrow::Borrow<T> for T
where T: ?Sized;
The conflict occurs because the type T
might be Wrapper<T>
, resulting in a conflicting implementation of impl Borrow<Wrapper<T>> for Wrapper<T>
. While it is not actually possible to write an implementation of Wrap
that leads to this kind of conflict – the resulting type would have an infinite recursion – I can understand that the compiler can't prove this and rejects the code.
It turns out that it is possible to convince the compiler that T
can't be Wrapper<T>
by introducing a dummy trait and using that as a trait bound on T
:
pub trait Dummy {}
impl<W, T> Borrow<T> for Wrapper<W>
where
W: Wrap<Wrapped = T>,
T: Dummy,
{
fn borrow(&self) -> &T {
&self.0
}
}
We would need to implement Dummy
on each type we want to use as Wrap::Wrapped
, but then this approach works. So far, so good.
However, if we remove the redundant type parameter T
and use W::Wrapped
instead, the code stops compiling again:
impl<W> Borrow<W::Wrapped> for Wrapper<W>
where
W: Wrap,
W::Wrapped: Dummy,
{
fn borrow(&self) -> &W::Wrapped {
&self.0
}
}
I think the compiler should accept this version of the code as well if it accepts the previous one. What is the difference between the two last implementations of Borrow
? Why does one compile, but the other doesn't?