Thoughts about multiple blanket impls to the same trait

Part 1. Drawing attention to the problem

Hello, I'm not a very experienced rustacean and initially I've stuck trying to write code like:

trait Container: IntoIterator { /* snip */ }
impl<T: Container> IntoIterator for T { /* snip */ }

But this is the wrong code because IntoIterator already has a blanket impl.

This is quite disappointing because in other languages I could implement the interface on the base class, but in Rust I must implement it manually on each type again and again - goodbye DRY (macros are not a graceful approach).

And moreover what if setting IntoIterator as supertrait of Container is an afterthought? We cannot add IntoIterator as supertrait because it would be a breaking change for our API because we cannot add corresponding blanket impl.

I discovered some proposals like specialization (seems like does not resolve this problem at all) and negative bounds (seems like it is a too big change and has some problems).

Then I've tried to find some other way to solve the problem and some thoughts are below. Maybe it has obvious disadvantages - at least I tried.

Part 2. Some thoughts

So. In the initial example above the compiler is worried about the overlap. But why every type must struggle and don't use ready impl because some other potential type can trigger overlap? What about just apply no one of conflicting impls in the case of the overlap?

In most cases a concrete type e.g. Container1 will not implement Iterator, so here the ready impl for IntoIterator will be used.

But let's imagine some strange type ContainerAndIterator that implements both Container and Iterator, in this case the compiler will just say that we must manually implement IntoIterator because it is supertrait.

What about the afterthought-case mentioned above? Let's say we have:

trait Container { /* snip */ }

And we want to add supertrait IntoIterator without breaking anything, we could write:

trait Container: IntoIterator { /* snip */ }
impl<T: Container> IntoIterator for T { /* snip */ }
impl<T: Container + Iterator> IntoIterator for T { /* snip */ }

And if the 2 impls are the same we could use the syntax sugar like:

trait Container: IntoIterator { /* snip */ }
impl<T: Container + ?Iterator> IntoIterator for T { /* snip */ }

Update: It seems like the above violates the orphan rules. Maybe we could weaken them for this special case e.g. because Container is local.

The way that I normally solve this is through a newtype:

struct ContainerIter<C>(pub C);

impl<C: Container> IntoIterator for ContainerIter<C> { ... }

But this feels clunky

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.