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