Home > Enterprise >  Understanding the nuances between collecting `flat_map` vs collecting `map` in rust
Understanding the nuances between collecting `flat_map` vs collecting `map` in rust

Time:12-08

I'm learning Rust using Advent of Code.
To resolve day 04 2021, I need to parse the following string:

88 67 20 19 15
22 76 86 44 73
 7 42  6 69 25
12 68 92 21 75
97 45 13 52 70

75 98 24 18 77
17 93 46 49 13
92 56 97 57 66
44  0 65 54 74
23  6 53 42 20

92 94  9 27 41
73 28 62 90 40
78  3 12 37 32
 8 86 91 16 30
84 38 68 11 19

Ideally, I would like to generate nested vectors Vec<Vec<&str>>.
My problem is that I don't understand why the following code doesn't work.

let bingo_sheet = grids_str
  split("\r\n\r\n")
  .map(|grid| grid.split_whitespace())
  .collect::<Vec<Vec<&str>>>();

I also don't understand the meaning behind cargo check. (Ok the trait doesn't exist. But why?)

error[E0277]: a value of type `Vec<Vec<&str>>` cannot be built from an iterator over elements of type `SplitWhitespace<'_>`
  --> src\main.rs:36:10
   |
36 |         .collect::<Vec<Vec<&str>>>();
   |          ^^^^^^^ value of type `Vec<Vec<&str>>` cannot be built from `std::iter::Iterator<Item=SplitWhitespace<'_>>`
   |
   = help: the trait `FromIterator<SplitWhitespace<'_>>` is not implemented for `Vec<Vec<&str>>`

After a bit of experimentation this usage of flat_map seems to work.

let bingo_sheet = grids_str
   .split("\r\n\r\n")
   .flat_map(|grid| grid.split_whitespace())
   .collect::<Vec<&str>>();

I don't understand what is happening here.
The flat_map code behaves as expected, but not the map code.

CodePudding user response:

The question has been answered briefly in the comments already while I wrote this but maybe a more detailed version still helps!

I also don't understand the meaning behind cargo check. (Ok the trait doesn't exist. But why?)

The std provides conversions that are used often but it doesn't do all of them for you.

the call to split_whitespace returns a SplitWhitespace struct that implements (in the rest of this I will simply say "is") Iterator. the call to map returns a Map struct which is an iterator over the return type of its given function, so it's an Iterator over an Iterator over string slices.

Because Map is an iterator, it has a collect method. As you can see in the docs, this method exists if there is a B that implements FromIterator<Self::Item>. This B is your Vec<Vec<&str>> and Self::Item is the item of Map, the SplitWhiteSpace struct. So collect is asking, "does Vec<Vec<&str>> implement FromIterator<SplitWhiteSpace>?, if yes, I exist, if not, I refuse to exist".

(FromIterator<SplitWhiteSpace> does not mean that Vec<T> can be made from the SplitWhiteSpace struct but that it can be made from some type I that is (or can be made into) an iterator over the item SplitWhiteSpace.)

So let's look into the Vec docs and find out if Vec<Vec<&str>> implements FromIterator<SplitWhiteSpace>. Vec does implement FromIterator<T> but only for Vec<T>, so in your case it implements FromIterator<Vec<&str>> but collect is asking it to implement FromIterator<SplitWhiteSpace>. It doesn't so your code doesn't compile.

In other words, when collecting a Map<X> into a Vec<Y>, X has to equal Y.

And this is why adding a collect() inside the .map works. Because that makes it a Map<Vec<&str>> which means the types match. flatmap also works because that creates a FlatMap<&str> which works similarly to Map and can be collected into a Vec<&str>. (flat_map does not need the collect inside its given function because it can handle iterators inside it)

  • Related