Converting iterators as a blanket implementation

Hi, I'm new to Rust and was recently working on a problem to convert a Vec<TheirAddress> to a Vec<OurAddress>, converting the address format of an API response to the address struct we use internally. I ended up settling on

let addresses = resp_addresses.iter().map(OurAddress::from).collect();

This works, and the resulting type of addresses is Vec<OurAddress>, but it seems like this is a common use case. Is there a more idiomatic way of doing this? For example, could their be a blanket implementation on From like

impl<U, T> From<Vec<U>> for Vec<T> 
where 
  T: From<U>,
{
  fn from(u: Vec<U>) -> Self {
    u.iter().map(T::from).collect()
  }
}

^ what I am attempt to convey here is for any type U and T where U can be converted into T you should be able to convert a Vec into a Vec. Does this already exist in the standard library and I missed it?

Further, could / should this be generalized to iterators like

impl<U, T, D> From<IntoIterator<U>> for D 
where 
  T: From<U>,
  D: FromIterator<T>,
{
  fn from(u: IntoIterator<U>) -> Self {
    D::from_iter(u.into_iter().map(T::from))
  }
}

This would allow anything that can be converted into an iterator of U to be converted into anything that can be created from an iterator of T if U can be converted into T. This would allow me to do things like

let resp_addresses = // Vec<TheirAddress>...

let addresses: BTreeSet<OurAddress> = BTreeSet::from(resp_addresses);

This seems like it would be really useful to allow for quickly converting between containers and the underlying data type in the process. Does this make sense or what am I missing?

This will probably one day be doable with just the normal .into(), but because of the blanket T: From<T> it needs specialization, which is not yet stable.

(The library cannot write impl<U> From<Vec<U>> for Vec<T> where T: From<U> because that overlaps with the From<Vec<T>> for Vec<T>> from the blanket.)

1 Like

Hey @scottmcm, this makes sense. I didn't know about specialization but it's good to know people are thinking about this.

I don't think that the latter blanket implementation I suggest suffers from this because

impl<U, T, D> From<IntoIterator<U>> for D where T: From<U>, D: FromIterator<T>

Does not overlap with

impl<T> From<T> for T

Because IntoIterator<U> is not the same as FromIterator<T> even if U = T, so if there are no other conflicts then this should be implementable. Maybe there is already a

impl<T> From<IntoIterator<T>> for FromIterator<T>

Which would overlap. Is there an easy way to search the stdlibs for trait implementations like this? Or do you have any suggestions for how I would go about testing if this is possible? I think this would need to go in the std::iter module because it deals with iterator traits and is too specific to for std::convert

You can always play around on the playground.

I've provided a scaffold you can work on, creating my own version of FromIterator, Iterator, and IntoIterator (I literally copy-pasted them from the std docs :stuck_out_tongue:) so we are free to add whatever impl blocks we want. The Orphan Rules aren't important for this exercise so using the normal traits would just get in our way.

I'm not sure if it's intended as pseudocode or not, but this isn't valid. IntoIterator and FromIterator are both traits, so you would have something like impl<T, I, F> From<I> for F where I: IntoIterator<T>, F: FromIterator<T>.

Also keep in mind that the Item in IntoIterator is an associated type and not a generic, so you should write IntoIterator<Item=T>.

Even if it were valid syntax I'm not sure this would be possible to express in Rust's type system. It feels under-constrained, kinda like in maths when you are trying to do simultaneous equations with 3 unknowns and 2 equations to work with. That's an intuitive feeling, but I'm sure if you played around with it rustc would yell about some type parameter not being constrained by the inputs or where clauses.

1 Like

Vec<T> implements both FromIterator<T> and IntoIterator<Item=T>, so it is valid for both F and I here. That reduces to:

impl<T> From<Vec<T>> for Vec<T> {}

which is in direct conflict with the blanket impl<T> From<T> for T.

Thanks so much for the info, and the playground link. I've taken your scaffolding and tinkered with it a bit.

The compiled is telling me the same thing

error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates

I took a look at E0207 and RFC 447, "No Unused impl Parameters" and it's clear the problem is that in my implementation

impl<U, T, F, I> MyFrom<I> for F
where
    T: MyFrom<U>,
    F: MyFromIterator<T>,
    I: MyIntoIterator<Item = U>,
{
    fn from(i: I) -> Self {
        // ...
    }
}

The parameter T isn't used in impl trait or self type, but I kind of assumed it would be implicit because F must implement MyFromIterator<T> which implies T is constrained / determinable by the implementation of F. I think I need to read the RFC over again and try to wrap my head around this a bit more.

Even if T were constrained, as @2e71828 pointed out, Vec<T> implements FromIterator<T> and IntoIterator<Item=T> so this overlaps with the reflexive From blanket implementation :frowning:


Is there a way to specify one type is not another? This would be useful to avoid overlap (but specialization might be a better solution)

It isn't determinable. The type F might implement both MyFromIterator<T1> and MyFromIterator<T2>.

3 Likes

:sweat_smile: this makes sense. Thank you :upside_down_face: