where
Self: Sized,
F: FnMut(Self::Item) -> B,
{
MyMap::new(self, map_fn)
}
fn filter(self, filter_fn: P) -> MyFilter
where
Self: Sized,
P: FnMut(&Self::Item) -> bool,
{
MyFilter::new(self, filter_fn)
}
}
```
### Map Adapter
This is a simplification of [std::iter::Map](https://doc.rust-lang.org/std/iter/struct.Map.html)
```rust
/// I is the iterator type the map method is applied on.
/// F is the type of the closure that is used to map the elements. It is constrained to be of type
/// `FnMut(Self::Item) -> B` in the MyIterator implementation
pub struct MyMap
where
I: MyIterator,
{
iter: I,
map_fn: F,
}
impl MyMap
where
I: MyIterator,
{
pub(crate) fn new(iter: I, map_fn: F) -> Self {
MyMap { iter, map_fn }
}
}
impl MyIterator for MyMap
where
I: MyIterator,
F: FnMut(I::Item) -> B,
{
/// The Item type of MyMap is the type returned by the map closure
type Item = B;
fn next(&mut self) -> Option {
if let Some(x) = self.iter.next() {
Some((self.map_fn)(x))
} else {
None
}
}
}
```
Lets examine the various generic variables we used here:
`I` - The MyMap struct owns the instance of the iterator we're mapping over. `I` is the type of this iterator. For example, if we are mapping over a `SliceIterator`, `I` will be `SliceIterator`.
`F` - The MyMap struct also owns the closure we use to map the items. `F` is the type of this closure. For example, if the closure is `|x| x * 2`, `F` will be `FnMut(&i32) -> i32`.
`B` - The map function return type needs to be defined as well. The return value of the map function is the item type of the MyMap iterator. For example, if the closure is `|x| x * 2`, `B` will be `i32`, and the MyMap iterator `next` method have `Option` as its return value.
### Filter Adapter
Dumbing down of [std::iter::Filter](https://doc.rust-lang.org/std/iter/struct.Filter.html)
```rust
/// I is the iterator type the filter method is applied on.
/// P is the type of the predicate function we invoke on each item
pub struct MyFilter
where
I: MyIterator,
{
iter: I,
filter_fn: P,
}
impl MyFilter
where
I: MyIterator,
{
pub(crate) fn new(iter: I, filter_fn: P) -> Self {
MyFilter { iter, filter_fn }
}
}
impl MyIterator for MyFilter
where
I: MyIterator,
P: FnMut(&I::Item) -> bool,
{
/// The Item type of MyFilter is the same as the Item type of the inner iterator
type Item = I::Item;
fn next(&mut self) -> Option {
// we iterate over the inner iterator until we find an item that passes the filter
while let Some(x) = self.iter.next() {
if (self.filter_fn)(&x) {
return Some(x);
}
}
None
}
}
```
Adapters like `Map` and `Filter` are internal to the iterator implementation, so you will rarely use them directly, nonetheless, let's see a usage example:
```rust
#[test]
fn my_map_filter_next_returns_next_item() {
let iter = SliceIterator::new(&[1, 2, 3]);
// note that the predicate closure is a reference to a reference. Check out the code to figure out why!
let filter = MyFilter::new(iter, |x: &&i32| **x % 2 == 0);
let mut map = MyMap::new(filter, |x| x * 2);
assert_eq!(map.next(), Some(4));
assert_eq!(map.next(), None);
}
```
## Collect
Most iterator usages will end with a call to the [collect](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect) method in order to collect the iterated items into a collection of a concrete type. Let's get under the hood of this one as well.
First, we expand the `MyIterator` trait to include the `collect` method:
```rust
pub trait MyIterator {
// ...
fn collect(self) -> B
where
B: MyFromIterator,
Self: Sized,
{
B::my_from_iter(self)
}
}
```
The `collect` method defines a return position generic type `B` which must implement the `MyFromIterator` trait.
## FromIterator trait
[std::iter::FromIterator](https://doc.rust-lang.org/std/iter/trait.FromIterator.html) is a trait that defines how to create a collection from an iterator. Previously, we defined the `SliceIterator` struct which converts a vector into an iterator. Now we need to define the opposite - a way to convert an iterator into a collection.
Let's define the `MyFromIterator` trait:
```rust
pub trait MyFromIterator {
fn my_from_iter(iter: I) -> Self
where
I: MyIterator- ;
}
```
`my_from_iter` allows us to convert anything that implements `MyIterator` into the trait implementing collection. We could use this to convert `SliceIterator`, `MyMap`, `MyFilter`, and any other `MyIterator` into a collection.
Note that for simplicity, we are not using the `IntoIterator` trait here, as used in the actual `std::iter::FromIterator` trait.
For example, let's implement `MyFromIterator` for `Vec` and `HashSet`. Both examples iterate over the target iterator and push each item into the constructed collection.
```rust
impl MyFromIterator for Vec {
fn my_from_iter(mut iter: I) -> Self
where
I: MyIterator
-
{
let mut vec = Vec::new();
while let Some(x) = iter.next() {
vec.push(x);
}
vec
}
}
impl MyFromIterator for HashSet
where
// we need to add these constraints because HashSet::new() requires them
T: Eq + Hash,
{
fn my_from_iter(mut iter: I) -> Self
where
I: MyIterator
- ,
{
let mut set = HashSet::new();
while let Some(x) = iter.next() {
set.insert(x);
}
set
}
}
```
Let's look at the `collect` usage example again:
```rust
let iter = SliceIterator::new(&[1, 2, 3]);
let v: Vec<_> = iter.collect();
```
With our simplified implementation, it actually expands to
```rust
fn collect(self) -> Vec
{
Vec::::my_from_iter(self)
}
```
and more specifically
```rust
Vec::::my_from_iter(iter);
```
Any other collection type that implements `MyFromIterator` can be used in the same way:
```rust
let s: HashSet<_> = iter.collect();
let b: BTreeSet<_> = iter.collect();
let l: LinkedList<_> = iter.collect();
```
Each one of these `collect` calls actually expands to a different `my_from_iter` implementation.
```rust
let s = HashSet::<_>::my_from_iter(iter);
let b = BTreeSet::<_>::my_from_iter(iter);
let l = LinkedList::<_>::my_from_iter(iter);
```
Now we know that `collect` is a thin sugar coating over `FromIterator` and `from_iter`, and in fact, does not violate Rust's strict type system.
## Dumbing up
Be sure to go over the [std::iter](https://doc.rust-lang.org/std/iter/index.html) documentation to see all the other iterator methods and adapters.
All the source code for this article can be found in the [src](./src/my_iterator.rs) directory.