Rust has functional programming features, namely the ability to use functions as arguments or return them from other functions. To better understand adapter methods such as map, it is worth first revisiting iterators and collections.
Iterator Vs collection
A collection is a data structure that holds values in memory eg. Vec<T>. An iterator however, does not store values and only provides a way to access a sequence of values and generate them often into another collection.
The iterator as defined in the iterator trait has a next method which returns the elements of the iterator Option<T> i.e. Some<T> or None. Other iterator methods are built on top of the next method.
Create an iterator from a collection
Here are 3 methods used to create an iterator from a collection.
1. iter()Provides an iterator of immutable references to all content of the collection.
2. iter_mut()
Provides an iterator of mutable references which means that the elements of the collection can be changed with this iterator.
3. into_iter()
Transforms the collection into an iterator when the collection is no longer needed. In other words, it transfers ownership of each element.
To produce a collection from the iterator from into_iter(), use collect(). The collect() method needs to know the type it is collecting whether inferred from the variable type or using something like vec.into_iter().collect::<Vec<_>>(). Elements of the collection generated have the same type as the ones in the original collection.
To collect iterator elements into a different type, use the following:
This leads us nicely to adapter methods.
Adapter methods
They take an iterator type and return another iterator. map and filter are the most common adapter methods.
The map method as seen in its source code is an iterator method. It takes a closure as an argument. The iterator will call the closure on every element to produce a new iterator.
It is worth mentioning at this point that iterators and their methods are lazy meaning that they execute when calling methods like next() and collect(), consumer methods.
What are some different flavors of the map() method?
filter_map
filter_map is used to create an iterator that both filters and maps, such that the chain of methods to do both is replaced by 1 method.
Given a vector of strings vec!["one", "1", "two", "2", "three", "3"], we want to produce another vector containing all values that can be parsed to type i32. We can do that using filter().map() or more efficiently using filter_map(). Here is the code for both:
Here is the code using filter_map():
map_windows [Nightly]
This method creates an iterator over all contiguous windows of size n. As the name indicates, it provides a rolling window of a certain length on which you can run a closure or an operation.
You can try this method locally using the Rust nightly build or on Rust playground. In the code below, the average of the rolling 3 numbers is computed.
find_map
This method helps find the first occurence of an element that satisfies certain criteria. For example if you are looking for the first integer in a vector of strings.
flat_map
With flat_map you can iterate over elements while flattening them at the same time. The example below demonstrates flattening arrays but this can be done to characters as well.
There are many other iterator methods in Rust. Although it might take a while to understand long lines with chained methods, I find they make the code richer with added variety.