Is it possible to implement `From<impl IntoIterator>` for a struct that itself `impl IntoIterator`?

Rust playground illustrating the issue

I have a struct (call it Collection) which implements IntoIterator. I'd like to be able to instantiate this struct using from, passing in another IntoIterator:

let collection = Collection::from([1, 2, 3]);

So, I believe I need to implement From<impl IntoIterator> for Collection. However, this results in conflicting implementations of From with the default blanket implementation From<T> for T.

  • Collection implements From<I> where I: IntoIterator
  • Collection implements IntoIterator
  • Collection could be I
  • This gives From<Collection> for Collection
  • Which has the same signature as the blanket From<T> for T, resulting in the conflict

I haven't been able to find any way around this... I was thinking I could exclude Collection from I, but this requires negative_impls, which is unstable.

This feels like a fairly common pattern? Looking at the docs for Vec<> I can see it implements From<> with brute force for the common iterable types, but I was hoping to be able to write something more generic.

I have of course already implemented FromIterator for Collection, so I can construct it with from_iter() – I just want to make the shorter from() work too.

Normally, FromIterator is used via .collect() on the iterator.

2 Likes

It's never possible to implement From to convert from or to all types satisfying a property. This is part of why conversion traits other than From exist at all — for example, IntoIterator is a conversion that is blanket implemented for all Iterators.

1 Like

That makes sense, thanks for explaining. I suppose the only way, then, is to explicitly implement From for some common types?

Hold on, that can't always be true, since this is possible:

impl<I,T> From<I> for Collection<T>
    where I: IntoIterator<Item = (T, T)>
{
    // --snip--
}

In this case the above conflict doesn't apply because Collection<T> implements IntoIterator<Item = T>, not IntoIterator<Item = (T, T)>. Is that clash just an annoying edge case then?

1 Like

Sorry, yes, you’re right; I forgot that the rules have more nuance than I said. It’s better to say: you can't write anything that conflicts with impl<T> From<T> for T.

It would be nice if there was a way to carve out that overlap by writing something like

impl<I,T> From<I> for Collection<T>
where
    I: IntoIterator<Item = T>,
    I != Collection<T>,

but that is in fact a very hard type-system problem in disguise as a simple one.

2 Likes

Coherence errors can be confusing in cases where there is no actual overlap, e.g.

  • due to future foreign implementations allowed under the orphan rules, which are intentionally considered overlapping even if they don't currently exist
  • due to limitations or flaws in the current coherence checking algorithms

But in the OP case there is actual overlap, which is at least easy to explain; I'm not sure I'd call it an edge case.[1]

...but that doesn't make it any less annoying I'm sure.


  1. Incidentally negative_impls doesn't help since you want the IntoIterator implementation; in fact delete that implementation and the From implementation works today. You'd need something like From being specializable. ↩︎

3 Likes

The closest thing you can get to a negative impl is involving your own type from your own crate that will be known not to implement a trait.

struct Wrapper<I>(I);

impl<I> From<Wrapper<I>> for Collection

Unfortunately, it's not transparent and needs to be used explicitly:

Collection::from(Wrapper(i))

It can be made nicer if you're doing this for your own trait, because then you can define trait MyFrom<T, WhoImplementsIt = MyMarkerType> and have separate impls for MyFrom<T, MyMarkerType> and MyFrom<T, SomeElsesMarkerType>. But in this case From is in std and you can't change it.

You can add inherent fn from to Collection to just get the Collection::from() syntax working when used directly, but of course that won't suffice in generic contexts that want the From trait.

2 Likes