I'm trying to think of reasons why the standard library developers chose to define a special FromIterator<A> trait, instead of just making do with From<Into<Iterator<Item = A>>>
Does FromIterator make life easier somehow? Like for type inference? Or are there historical reasons behind this?
I think the most solid issue is that you can't blanket impl From. So you can't do impl<I: Iterator<Item = T>, T> From<I> for Vec<T>. But even if you could, it's good to have explicit conversion methods.
The core reason to need this is the lack of specialization (which has huge challenges around lifetimes to resolve, so may never happen).
Assume that I have the following implementations:
impl<T: Clone> From<&[T]> for Vec<T> { … }
impl<T> From<Into<Iterator<Item = T>>> for Vec<T> { … }
impl<T: Copy> Into<SliceIterator<T>> for &[T] { … }
impl<T> Iterator for SliceIterator<T> {
type Item = T;
…
}
Which implementations should the compiler choose in the following case, and how does it do so reliably?
let items: &[u32] = &[1, 2, 3, 4].as_slice();
let v = Vec::from(items);
The problem is that the T in &[T] in this case is u32, which implements Clone and Copy. As a result, we have ambiguity; we could mean the first implementation (From<&[T]>), since we meet all the requirements. However, we could also mean the second implementation (From<Into<Iterator<Item = T>>>), since &[u32] meets all the requirements for impl<T: Copy> Into<SliceIterator<T>> for &[T].
Separating them out means that you can insist that the programmer chooses which one to use:
impl<T: Clone> From<&[T]> for Vec<T> { … }
impl<Iter, T> FromIterator<Iter> for Vec<T> where Iter: Iterator<Item = T> { … }
impl<T: Copy> Into<SliceIterator<T>> for &[T] { … }
impl<T> Iterator for SliceIterator<T> {
type Item = T;
…
}
If I use Vec::from, I get the first implementation; if I use Vec::from_iter, I get the second.
I think that overloading From/Into and similar with different jobs is not helpful for clear code and good programs.
But with that aside, Rust is more concrete about types than this. We don't actually want to write From<Into<Iterator<Item = A>>>, because From<X> should have X where X is a type and Into and Iterator are traits!
So you'd need to expand it, something like From<I1>where I1: Into<I2>, I2: Iterator<Item=A> - now we're naming concrete types A, I1 and I2. I think you can make something like this “work” - exercise for the reader? - but it will make most other parallel From impls impossible, because the compiler can't be sure they don't overlap.
It would be possible to ban all functions exception .into(), and make you write everything with different types instead. But that would obviously not be good.
Different traits for different things is a good thing, because it makes it clearer what you can do with things, makes it easier to look at the docs for those traits, …