Iterator producing iterator in Rust is really helpful.

Reading Time: 4 minutes

If you are a little bit familiar with programming, you might have heard about an iterator. It is quite useful as it allows to access each item of an iterable and perform various operations as well. Let us explore it more and learn about some cool methods of iterators in Rust that produce other iterators.

Introduction

Iterators are the objects that iterate over a collection of values and return each item of the collection. Like other programming languages, Rust also has iterators which can be used by implementing Iterator Trait.

The Iterator Trait looks something like this –

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    ...
}

The type Item defines the type of values being iterated over. There are a whole bunch of other methods of Iterator trait but the next function is the required method of this trait. The other methods include map(), filter(), zip(), etc. We will see them later in the blog.

Understand with example

Here is an example of an iterator in action. Let us look at this for more clarity.

#[test]
fn iter_demo()
{	
    let numbers = vec![1, 2, 3];

    let mut num_iter = numbers.iter();

    assert_eq!(num_iter.next(),Some(&1));
    assert_eq!(num_iter.next(),Some(&2));
    assert_eq!(num_iter.next(),Some(&3));
    assert_eq!(num_iter.next(),None);
}

In this example, the iter function of the vector returns an iterator that can iterate over the vector numbers. The num_iter is made mutable because the next method used in the next few statements changes the internal state of the iterator. So the next method keeps returning the next value in the vector numbers.

Keep in mind, in this case, the iter() function of vectors implements the Iterator trait for us. But if we want an iterator for our own type we need to explicitly define the next method ourselves.

Methods that produce another Iterator

There are various methods of iterators that produce another iterator. It’s quite weird to listen to it for the first time but there exist some methods like these. Let’s look at this one by one.

map() method

This is a method of the Iterator Trait that takes a closure and produces an iterator which calls that closure on each element. map() takes something that implements FnMut Trait as an argument which would eventually be a closure. A closure is an anonymous function without any name. Here is an example of the map().

let numbers: Vec<i32> = vec![1, 2, 3];

numbers.iter().map(|item| item + 1);

If you run this code, it will show this warning.

This warning means that iterators in rust are lazy and they do nothing unless consumed. So we need to consume this iterator to force it to use the map() method. We remove this warning by doing this.

let numbers: Vec<i32> = vec![1, 2, 3];

let output: Vec<i32> = numbers.iter().map(|item| item + 1).collect();

print!("{:?}",output);

The collect method consumed the iterator and transformed it into a collection. The output vector will contain [2, 3, 4] as the map called the closure on each element.

zip() method

zip() returns a new iterator that will iterate over two other iterators and return a tuple where the first element comes from the first iterator, and the second element comes from the second iterator.

It is a very useful method of Iterator Trait. Here is an example of this.

let emp_no = vec![1, 2, 3];

let age = vec![20, 30, 35];

let iter_emp = emp_no.iter();

let iter_age = age.iter();

let zip_iter = iter_emp.zip(iter_age);

for item in zip_iter{
    println!("({}, {})", item.0,item.1);
} 

The above code defines two iterators and then use the zip() method to zip up the elements of two vectors in tuples. The output will look like this-

(1, 20)
(2, 30)
(3, 35)

filter() method

The filter method creates an iterator that uses a closure to determine if an element should be yielded. This is one of the most used methods of iterators because of its functionality. Here is an example of this.

let numbers: Vec<i32> = vec![0, 1, 2];

let mut num_iter = numbers.iter().filter(|num| num.is_positive());

assert_eq!(num_iter.next(), Some(&1));
assert_eq!(num_iter.next(), Some(&2));
assert_eq!(num_iter.next(), None);

numbers is an i32 vector. num_iter is an iterator over numbers vector and uses filter() method to filter out non-positive numbers and yield only positive numbers.

Let us look at an interesting thing in the field() method. Have a look at the following code.

let num = [0, 1, 2];

let mut iter = num.iter().filter(|item| **item > 1); 

assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), None);

If you observed the above code, you would have noticed that there are two * before the item in closure. This is because the closure passed to filter() method takes a reference. Below is the filter() method in the Iterator trait and you can see P is bound by FnMut and &Self::Item is passed as an argument.

pub fn filter<P>(self, predicate: P) -> Filter<Self, P>
where
    P: FnMut(&Self::Item) -> bool, 

Also, many iterators iterate over references. Therefore to access the element we need to dereference it two times as the item is a reference to reference.

So, this was all from my side. I will be back with another blog.

If you want to read more content like this?  Subscribe to Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe to Rust Times Newsletter: https://bit.ly/2Vdlld7.


Knoldus-blog-footer-image

1 thought on “Iterator producing iterator in Rust is really helpful.5 min read

  1. “an interesting thing in the field() method.”
    I think you mean filter() here.

    You say zip() is very useful but your example isn’t. I assume people chain zip() with another iterator that does something with the tuples.

    Your last example changes from a vector to an array; would a vector require the same double dereference in the closure? It’s confusing that you reuse the num variable name.

    Thanks!

Comments are closed.

%d bloggers like this: