Why do these impls conflict?

Viewed 79

I have a trait representing a storage type, with a method to take out that storage as either a boxed value or unboxed value.

I have a second trait to allow conversion from a container using that storage trait into an arbitrary but specific type. I want to do blanket implementations from the container type either boxed or unboxed types.

Here is a simplified example. In my actual code, I'm not actually working with box, but rather with memory allocated in slightly complicated ways, but box demonstrates the issue I've run into:

enum MaybeBoxed<T> {
    Boxed(Box<T>),
    Unboxed(T),
}

trait Storage {
    type Stored;
    
    fn into_inner(self) -> MaybeBoxed<T> where Self: Sized;
}

struct BoxedStorage<T>(Box<T>);

impl<T> Storage for BoxedStorage<T> {
    type Stored = T;
    
    fn into_inner(self) -> MaybeBoxed<T> {
        MaybeBoxed::Boxed(self.0)
    }
}

struct UnboxedStorage<T>(T);

impl<T> Storage for UnboxedStorage<T> {
    type Stored = T;
    
    fn into_inner(self) -> MaybeBoxed<T> {
        MaybeBoxed::Unboxed(self.0)
    }
}

struct Container<S: Storage>(S);

trait MakeInto<T> {
    fn make_into(self) -> T;
}

impl<S: Storage> MakeInto<S::Stored> for Container<S> {
    fn make_into(self) -> S::Stored {
        match self.0.into_inner() {
            MaybeBoxed::Boxed(boxed) => *boxed,
            MaybeBoxed::Unboxed(unboxed) => unboxed,
        }
    }
}

impl<S: Storage> MakeInto<Box<S::Stored>> for Container<S> {
    fn make_into(self) -> Box<S::Stored> {
        match self.0.into_inner() {
            MaybeBoxed::Boxed(boxed) => boxed,
            MaybeBoxed::Unboxed(unboxed) => Box::new(unboxed),
        }
    }
}

Now, it seems like these last two impls should be exclusive, because S::Storage is an associated type, so it seems like <Container<S> as MakeInto<S::Stored>> ought to be provably a different type from <Container<S> as MakeInto<Box<S::Stored>>>, since even if S::Stored is Box<U>, the former becomes MakeInto<Box<U>> and the latter becomes MakeInto<Box<Box<U>>.

However, the compiler doesn't agree:

error[E0119]: conflicting implementations of trait `MakeInto<std::boxed::Box<_>>` for type `Container<_>`
  --> src/main.rs:47:1
   |
38 | impl<S: Storage> MakeInto<S::Stored> for Container<S> {
   | ----------------------------------------------------- first implementation here
...
47 | impl<S: Storage> MakeInto<Box<S::Stored>> for Container<S> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Container<_>`

playground link

In a previous iteration of this codebase, before I made Storage a generic type, I used a fixed set of storages and defined separate impls for each of them. At that time, I was able to separately define MakeInto<T> and MakeInto<Box<T>> for the same Container<S> with impls like impl<T> MakeInto<T> for Container<BoxedStorage<T>> and impl<T> MakeInto<Box<T>> for Container<BoxedStorage<T>> without running into conflicting impls. playground link.

In current stable rust, is there any way to convince the compiler that these impls are in fact disjoint? Or are they really not disjoint and there's some case where they overlap that I've missed?

1 Answers

The compiler sadly isn't smart enough to figure out that kind of logic.

You can solve this situation by introducing another trait:

enum MaybeBoxed<T> {
    Boxed(Box<T>),
    Unboxed(T),
}

trait Storage {
    type Stored;

    fn into_inner(self) -> MaybeBoxed<Self::Stored>
    where
        Self: Sized;
}

struct BoxedStorage<T>(Box<T>);

impl<T> Storage for BoxedStorage<T> {
    type Stored = T;

    fn into_inner(self) -> MaybeBoxed<T> {
        MaybeBoxed::Boxed(self.0)
    }
}

struct UnboxedStorage<T>(T);

impl<T> Storage for UnboxedStorage<T> {
    type Stored = T;

    fn into_inner(self) -> MaybeBoxed<T> {
        MaybeBoxed::Unboxed(self.0)
    }
}

struct Container<S: Storage>(S);

trait ConvertFrom<S> {
    fn convert_from(s: S) -> Self;
}

impl<S> ConvertFrom<MaybeBoxed<S>> for S {
    fn convert_from(s: MaybeBoxed<S>) -> Self {
        match s {
            MaybeBoxed::Boxed(boxed) => *boxed,
            MaybeBoxed::Unboxed(unboxed) => unboxed,
        }
    }
}

impl<S> ConvertFrom<MaybeBoxed<S>> for Box<S> {
    fn convert_from(s: MaybeBoxed<S>) -> Self {
        match s {
            MaybeBoxed::Boxed(boxed) => boxed,
            MaybeBoxed::Unboxed(unboxed) => Box::new(unboxed),
        }
    }
}

trait MakeInto<T> {
    fn make_into(self) -> T;
}

impl<S: Storage, T> MakeInto<T> for Container<S>
where
    T: ConvertFrom<MaybeBoxed<S::Stored>>,
{
    fn make_into(self) -> T {
        T::convert_from(self.0.into_inner())
    }
}

fn main() {
    let a1 = Container(BoxedStorage(Box::new(10u32)));
    let a2 = Container(BoxedStorage(Box::new(11u32)));
    let b1 = Container(UnboxedStorage(12u32));
    let b2 = Container(UnboxedStorage(13u32));

    let out_a1: u32 = a1.make_into();
    let out_a2: Box<u32> = a2.make_into();
    let out_b1: u32 = b1.make_into();
    let out_b2: Box<u32> = b2.make_into();

    println!("{}", out_a1);
    println!("{}", out_a2);
    println!("{}", out_b1);
    println!("{}", out_b2);
}
10
11
12
13
Related