I first discovered this issue, and then asked this question on StackOverflow: Why do I need to implement From
for both a value and a reference? Shouldn't methods be automatically dereferenced or borrowed?
It was discovered that the problem was the blanket trait implementation, which caused automatic dereferencing or borrowing to no longer work. I then decided to raise this as an issue on GitHub: Automatic dereferencing or borrowing on methods that exist behind a blanket (generic) trait implementation.
What is the problem?
The behaviour of a Rust method call is described in The Rust Reference, whereby it states that "when looking up a method call, the receiver may be automatically dereferenced or borrowed in order to call a method." The exact specification of this behaviour is explained in more detail in the chapter.
To do this a list of "candidate receiver types" is constructed. For example, given a "receiver type" of &usize
it would be expanded through repeated dereferences to become &usize
and usize
. Next, as it has no unsized coercion, the list would be unchanged. Then, for each type in the list T
, both &T
and &mut T
would be added immediately after T
, thefore the list would become &usize
, &&usize
, &mut &usize
, usize
, &usize
and &mut usize
. Finally, any "visible method with a receiver of that type" is discovered. In the event that multiple matches are discovered, then this is an error and the exact method must be disambiguated.
Now, given a type T
where From<T> for U
is implemented and thus Into<U> for T
is also implemented. Therefore, we should have the method U::from(T)
and T::into(U)
. When I call T::into(U)
it should and does work.
But, now when I call &T::into(U)
, it does not find an exact match. Since From<&T> for U
is not implemented therefore Into<U> for &T
is not implemented. However, it should still be possible to find a match through the automatic dereference and borrow procedure that was described above, but it does not.
Why is it a problem?
As explained in the answer on StackOverflow the reason for this is the blanket implementation of the Into
trait. Due to the current way Rust attempts to resolve methods it appears to match the blanket implementation, but before it is fully resolved, therefore when the generic types are filled in there is actually no match.
I have created this reproduction on the Rust playground whereby you can uncomment section (A)
, (B)
, (C)
or (D)
to see the different behaviours. This example should be the easiest way to get a good understanding of this issue.
How can it be fixed?
I don't know if this can be fixed, but I believe the lookup routine could be adjusted. I expect there are other cases where this could be a problem, however the From
and Into
traits are the only place I have discovered this issue so far...
If it can be fixed, should it? Would this be a breaking change that requires a new edition of Rust?
EDIT: I should expand on how it should be adjusted. In the event that a blanket implementation is found, Rust should then check that trait for a concrete implementation under the constraints of the blanket implementation. The search should continue when no concrete implementation is found, instead of the current behaviour of being terminated. Would this cause a problem? Maybe. I guess the blanket implementation may one day be satisfied, which should therefore be an error and "the exact method must be disambiguated". However, if I am not mistaken this error can already occur in the current process.