RustyBob
RustyBob

Reputation: 43

How to determine the resulting type of a Rust iterator?

I am working through the rustlings exercises to learn Rust. I have reached the iterator3.rs exercise and am stuck. This exercise asks me to provide a line of code that will map results from one type to another as part of the operation; I need to fill in the line x= with the correct operation. There are two parts - the first reads in part:

let numbers = vec![27, 297, 38502, 81];
let division_results = numbers.into_iter().map(|n| divide(n, 27));
let x = ???
assert_eq!(format!("{:?}", x), "Ok([1, 11, 1426, 3])");

The next is the same with a slightly different format for the assertion of the output:

assert_eq!(format!("{:?}", x), "[Ok(1), Ok(11), Ok(1426), Ok(3)]");

I believe I understand that the first instance needs to return a Result that contains either a Vector of i32 or some error type. The second needs to return a Vector of Results that each have a i32 or an error type.

I am, however, generally having difficulty understanding how to determine what type is returned by the combinations of into_iter, map, and collect. I could use some help in learning how to reason about it or in getting compiler assistance.

Here is where I am so far:

I don't understand what the resultant type of division_results is. I have tried to use compiler error messages as well as the answer of this question to find out, but the results are opaque to me, perhaps because of lazy evaluation. For example, just replacing x with division_results so that assert will show the types, like this:

assert_eq!(format!("{:?}", division_results), "Ok([1, 11, 1426, 3])");

Gives me this result:

left: `"Map { iter: IntoIter([27, 297, 38502, 81]) }"`,
right: `"Ok([1, 11, 1426, 3])"`', exercises/standard_library_types/iterator3.rs:75:9

And it isn't clear what the left side results are as the iteration and map have not happened. The other various things I have tried provide similar results.

Thinking that the problem is lazy evaluation, I have also tried using collect to see if that will force evaluation. For example, calling collect at the end of the division_results line like this:

division_results = numbers.into_iter().map(|n| divide(n, 27)).collect();

Provides an error:

cannot infer type consider giving `division_results` a type

When I modify collect to say:

let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect::<i32>();

I get an error that I thought gave me a hint at the right type:

let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect::<i32>();
   |                                                                           ^^^^^^^ a collection of type `i32` cannot be built from `std::iter::Iterator<Item=std::result::Result<i32, DivisionError>>`

So I tried with the type shown in the error message:

let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect::<Result<i32, DivisionError>>();

Only to get this error:

let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect::<Result<i32, DivisionError>>();
   |                                                                           ^^^^^^^ a collection of type `i32` cannot be built from `std::iter::Iterator<Item=i32>`

Clearly I am missing something. Perhaps you can tell me what?

Upvotes: 4

Views: 696

Answers (1)

Sven Marnach
Sven Marnach

Reputation: 602635

The map() method on an iterator adapter; it takes an iterator, and returns another iterator, but it does not consume any items from the original iterator by itself. The return type Map is a wrapper around the original iterator that applies the provided closure to every item when they are consumed.

If you want the Map to actually do something, you need to consume the iterator. The most common ways to do so are for loops and the collect() method (but there are many other methods that consume the iterator, like sum(), count(), fold(), max(), …). In this particular case, calling the collect() method is most appropriate, since you want to collect the results in a vector.

You already figured out that the desired type for x is a Result wrapping a vector of i32 or an error, or Result<Vec<i32>, DivisionError> in Rust syntax. Since collect() can produce many different return types, we need to tell the compiler which one we want. One way to do so is to explicitly specify the type of x:

let x: Result<Vec<i32>, DivisionError> = division_results.collect();

This uses an implementation of the FromIterator trait that allows to collect an iterable of Results into a Result wrapping a collection of values.

The other case you mentioned is very similar. This time, the target type is a vector of Result instances, so all you need to do is specify the different type. This will automatically select the right implementation of FromIterator for you:

let x: Vec<Result<i32, DivisionError>> = division_results.collect();

Upvotes: 7

Related Questions