Blanket From<_> impl conflicting with core

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.

Full example code is on the Rust playground and also inside the spoiler.

Full code
trait Deferable: Sized {
    type Id;
    fn get(&self) -> Option<Self>;
    fn get_or_load(&self) -> Self;
}

enum Defer<T>
where
    T: Deferable
{
    Deferred(<T as Deferable>::Id),
    Loaded(T),
}

impl<T> From<T> for Defer<T>
where
    T: Deferable
{
    fn from(t: T) -> Self {
        Self::Loaded(t)
    }
}

impl<I, T> From<I> for Defer<T>
where
    T: Deferable<Id = I>,
{
    fn from(i: I) -> Self {
        Self::Deferred(i)
    }
}

It's not that the two implementations are equivalent, but they can overlap non-equivalently.

struct Foo;
struct Bar;
impl Deferable for Foo { ... }
impl Deferable for Bar { type Id = Foo; ... }

Your two From implementations apply to Foo, simultaneously. Therefore, you cannot have both.

[This answer was incorrect; see below.]

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.


  1. note that this is a different variable T! ↩︎

  2. 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? ↩︎

2 Likes

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.

That's disappointing. I was hoping that

impl <Bar, Foo> From<Foo> for Defer<Bar>

and

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

let d: Defer<Bar> = Foo.into();

and

let d: Defer<Foo> = Foo.into();

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.

1 Like

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 IntoDefer::<Bar>::from(foo).

The real conflict is as @steffahn described.

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”.

5 Likes

The good news is that being an enum, you can simply make a Defer instance without requiring From:

let d = Defer::Loaded(value);

Is there a reason you wanted to use From specifically? To create Loaded and Deferred values?

You might like to import the variants directly (like std does in the prelude to provide Some and None):

use deferred::Defer::{Loaded, Deferred};

let d = Loaded(value);

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.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.