Late Nighter
Late Nighter

Reputation: 119

How does the Iterator::collect function work?

I am trying to understand the full capabilities of collect function by going through some documentation. I am running into some challenges, specifically in the last example quoted on the page (also listed below, with my comments inline)

let results = [Ok(1), Err("nope"), Ok(3), Err("bad")];

let result: Result<Vec<_>, &str> = results.iter().cloned().collect();

// gives us the first error <-- Point 1
assert_eq!(Err("nope"), result);

let results = [Ok(1), Ok(3)];

let result: Result<Vec<_>, &str> = results.iter().cloned().collect();

// gives us the list of answers
assert_eq!(Ok(vec![1, 3]), result);

I followed up this code with some of my own (shown below)

let results: [std::result::Result<i32, &str>; 2] = [Err("nope"), Err("bad")];

let result: Vec<Result<i32, &str>> = results.iter().cloned().collect();

// The following prints <-- Point 2
// "nope"
// "bad"
for x in result{
    println!("{:?}", x.unwrap_err());
}

Looking at the implementation of the FromIterator trait on the Result struct, we see that it mentions that "Takes each element in the Iterator: if it is an Err, no further elements are taken, and the Err is returned. Should no Err occur, a container with the values of each Result is returned.

This explanation is in line with the result that is seen at Point 1 but doesn't seem to work with Point 2. In point 2 I was expecting only "nope" to be printed instead of both values.

Hence, I am trying to understand where this (selective) conversion is happening and running into a challenge.

If we look at the method definition itself we see the following.

#[inline]
fn from_iter<I: IntoIterator<Item=Result<A, E>>>(iter: I) -> Result<V, E> {
    // FIXME(#11084): This could be replaced with Iterator::scan when this
    // performance bug is closed.

    iter::process_results(iter.into_iter(), |i| i.collect())
}

It shows that the into_iter() method is being called on the iterator. Searching for into_iter gives two implementations

#[stable(feature = "rust1", since = "1.0.0")]
impl<T, E> IntoIterator for Result<T, E> {
    type Item = T;
    type IntoIter = IntoIter<T>;

    /// Returns a consuming iterator over the possibly contained value.
    ///
    /// The iterator yields one value if the result is [`Result::Ok`], otherwise none.
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```
    /// let x: Result<u32, &str> = Ok(5);
    /// let v: Vec<u32> = x.into_iter().collect();
    /// assert_eq!(v, [5]);
    ///
    /// let x: Result<u32, &str> = Err("nothing!");
    /// let v: Vec<u32> = x.into_iter().collect();
    /// assert_eq!(v, []);
    /// ```
    #[inline]
    fn into_iter(self) -> IntoIter<T> {
        IntoIter { inner: self.ok() }
    }
}

#[stable(since = "1.4.0", feature = "result_iter")]
impl<'a, T, E> IntoIterator for &'a Result<T, E> {
    type Item = &'a T;
    type IntoIter = Iter<'a, T>;

    fn into_iter(self) -> Iter<'a, T> {
        self.iter()
    }
}

However, in my limited understanding of the language none seem to be able to explain what the documentation says as well as what is happening in Point 2.

Could someone please explain how this is working or point me to the right place in source where such selection logic is implemented?

What I want to understand is not why we get all values in a vector and only one in the result, but a. where is the code/logic to select the first Err from a list of values and b. why are multiple Err values selected when the result is collected in a list (when according to the documentation it should be only the first Err value)

Upvotes: 9

Views: 34018

Answers (2)

Shepmaster
Shepmaster

Reputation: 430673

Iterator::collect is defined as:

fn collect<B: FromIterator<Self::Item>>(self) -> B
where
    Self: Sized,
{
    FromIterator::from_iter(self)
}

All of the interesting implementation is done via the FromIterator trait. Every type can choose how to implement this trait as makes sense.

The implementation for Result and Vec are both optimized and abstracted, so it's not trivial to point to one line of code and say "here's where it is".

Result

This calls iter::process_results, an internal helper method for "lifting" an iterator of Results (you can see something similar in Itertools::process_results). If any Result::Err is encountered, then the iteration stops with that error. Otherwise, the closure is executed with an iterator containing only the success values. The closure calls Iterator::collect on the inner collection type, producing a Vec of the successful values.

Vec

This performs some internal specialization indirection via the SpecFromIter helper trait. Following through all of that, you'll see that an empty Vec is created and then extended with all of the items in the iterator.

Upvotes: 6

mcarton
mcarton

Reputation: 30001

In this example

let result: Vec<Result<i32, &str>> = results.iter().cloned().collect();

you do not collect into a Result, but into a Vec, hence all values are collected, untouched. This is expected from Vec.

This is fundamentally different from

let result: Result<Vec<_>, &str> = results.iter().cloned().collect();

where you collect into a Result, which does filter elements depending on whether an Err is found or not. This comes from impl<A, E, V> FromIterator<Result<A, E>> for Result<V, E> where V: FromIterator<A>,.

Upvotes: 13

Related Questions