13.8.0 Before We Begin
During its design, Rust drew inspiration from many languages, and functional programming had a particularly strong influence on Rust. Functional programming often includes passing functions as values to parameters, returning them from other functions, assigning them to variables for later execution, and so on.
In this chapter, we will discuss some Rust features that are similar to what many languages call functional features:
- Closures
- Iterators (this article)
- Improving the I/O Project with Closures and Iterators
- Performance of Closures and Iterators
If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.
13.8.1 Creating a Custom Iterator With the Iterator Trait
The main step is just one: provide an implementation of next.
Take an example:
Build an iterator that traverses from 1 to 5
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
First create a struct called
Counter. It has acountfield used to store the value needed during iteration, that is, the iterator’s state. Thecountfield is private rather thanpubso that theCounterstruct manages its own value independently.Then write an associated function
newon the struct to create a new instance and make sure the new instance starts from 0.Next, implement the
Iteratortrait forCounter. TheIteratortrait has an associated typetype Itemand anextmethod. First set the associated type tou32, which means writingtype Item = u32;. This syntax will be covered in detail in Chapter 19; for now, just know that this iterator returnsu32.The return type of
nextisOption<Self::Item>. Since the associated type above isu32, you can think of it asOption<u32>. When thecountfield is less than 5, it keeps increasing by 1; when it is 5 or greater, it returnsNone. That guarantees iteration from 1 to 5.
Now let’s implement something more complex:
Use a Counter struct in two forms: one from 1 to 5 and another from 2 to 5. Multiply each pair of elements from the two iterators, keep only the elements in the new iterator that are divisible by 3, and then return the sum of those elements
fn using_other_iterator_traits_methods() {
let sum: u32 = Counter::new()
.zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
}
- I wrote it on separate lines because the chained call would be too long on one line. If the chain is not long, there is no need to split it across lines.
- The
zipmethod pairs each element from two iterators to form a new iterator. The elements of that new iterator are tuples, and each tuple has two values, one from each iterator. -
Counter::new()creates aCounterthat goes from 1 to 5, andCounter::new().skip(1)creates aCounterthat skips the first value, so it goes from 2 to 5. When the two are zipped together, the result looks like this:
Counter::new() |
Counter::new().skip(1) |
|
|---|---|---|
| Tuple 0 | 1 | 2 |
| Tuple 1 | 2 | 3 |
| Tuple 2 | 3 | 4 |
| Tuple 3 | 4 | 5 |
PS: Counter::new() does not iterate to 5 because Counter::new().skip(1) is None at that point, so no more values are produced.
-
maptakes a closure that is applied to each element of the iterator. It transforms each element of the current iterator into another element, and those new elements form a new iterator. In this example, it multiplies the two values in each tuple stored in the iterator to produce a new iterator. -
filterkeeps the values divisible by 3 and forms a new iterator through the closure. -
sumconsumes all elements of the iterator and adds them together.
The final result should be 18.
Top comments (0)