Good morning, all! I'm implementing an enum Defer<T> to represent a value that can be lazily loaded from an ID, with two variants — Unloaded(I) which holds the ID, and Loaded(T) which holds the loaded value. This enum is bounded to a trait Deferable with an associated type Id.
What is causing me issues is that I am trying to implement blanket From implementations for both T and <T as Deferable>::Id. From<T> works fine, but when I try to implement From<<T as Deferable>::Id> the compiler complains that I am conflicting with the blanket From<T> for T implementation in core::convert.
So it looks as though the compiler is somehow reasoning that impl<I, T> From<I> for Defer<T> where T: Deferable<Id = I> is equivalent to impl<T> From<T> for Defer<T>, and I'm really unsure how it's coming to that conclusion.
error[E0119]: conflicting implementations of trait `From<Defer<_>>` for type `Defer<_>`
--> src/main.rs:28:1
|
28 | / impl<I, T> From<I> for Defer<T>
29 | | where
30 | | T: Deferable<Id = I>,
| |_________________________^
|
= note: conflicting implementation in crate `core`:
- impl<T> From<T> for T;
The overlap occurs if you have some type Foo with an impl Deferable for Foo { type Id = Defer<Foo> }.
Then your problematic impl applies with parameters:
I = Defer<Foo>
T = Foo
(the requirement of T: Deferable<Id = I> thus is Foo: Deferable<Id = Defer<Foo>>, the impl I just mentioned)
giving rise to From<Defer<Foo>> for Defer<Foo>, which overlaps with the From<T> for T[1] blanket impl.
Edit: The overlap that @kpreid identified is a different one; also relevant eventually[2], but not the first issue the compiler decided to complain about.
I’m not 100% sure they exactly gave the most problematic example; wouldn’t it only really be a problem once you have Foo: Deferable<Id = Foo> defering to itself? ↩︎
That makes sense. I hadn't considered that you could, pathologically, say that a type's own Defer wrapper is it's Deferable::Id type. Do you know off the top of your head if there's some way to prevent that from happening — to, in essence, say where T: Deferable<Id = I & !Defer<T>>. I don't think that kind of type reasoning is available, but if I were wrong that would be awfully convenient.
impl <Foo, <Foo as Deferable>::Id> From<Foo> for Defer<Foo>
would be... well, not reconcilable automatically, of course, but I was hoping that I would be given the option of using a turbofish or explicit type annotations to disambiguate between
I personally don’t love these restrictions either. In my mind, it should probably be made possible to somehow declare “these From implementations are desired, please complain to users instead, if they happen to write any problematic Deferable implementations”, at least in certain simple situations (notably the Deferable trait and the Defer enum and those From implementations are all in the same crate here).
Then ideally, it only creates a compilation error if someone actually writes an impl for Deferable with Id = Self or Id = Defer<Self>. Just like From with its blanket impl is a trait that compiles fine, but creates a compilation error if someone actually writes an impl for From<T> with T = Self.
But the compiler does not support anything like that, and it’s probably pretty complicated to design, anyway.
Sorry, I misunderstood the situation. You can in fact disambiguate implementations exactly that way, as long as they are uniquely identifiable by source and result types. If you want to do so on the function call itself, it's easiest to do with From rather than Into — Defer::<Bar>::from(foo).
The compiler is not a human software designer. It doesn't – can't possibly – reason about constraints outside the type system. There's nothing on the level of typing logic alone that would prevent a trait impl for a type where some associated type happens to be Self (in fact that's quite useful at times).
So there's absolutely no reason to expect that the conpiler follow some human-created, informal domain rules. You either encode it in the type system, or it doesn't exist, case closed.
I don’t think there was any such expectation anyways: I think this is why @dbg also said in their reply “This makes sense.”, and then essentially asked whether or not there’s some way to encode it in the type system, while already stating pessimistically they “don't think that kind of type reasoning is available”.
Mostly for function bounds: it would have been nice to be able to create functions which took arguments of type U: Into<Defer<T>> so that I could accept either a thing or an ID that could then be lazily turned into that thing at a later time. It's ultimately not a big deal, but would have been a nice little win in terms of not making the callers do work.