Working with iterators in Rust — A brief Introduction

In this short tutorial, we’ll look at Iterators in Rust, and how to work with for loops using iterators — both mutably and immutably. Iterators, like the name implies, allow us to loop through list list likedata structures in rust. It can be a Vector, a Hashmap, or even a custom type that can implement Iterator traits.

Since we are working with Vectors or references in general ( which are stored on heap ), we would have to follow rules for references even when dealing with primitive types such as integers and floats. Let’s take two simple examples to understand this.

Setup a new Project

For this tutorial, we’ll create a new rust Project as a library.

cargo new --lib map-iterator-demo

Let’s add the following code to src/lib.rs file .This is the starting point of our library.

We’ll create a new vector with 10 elements and create a new iterator for the list.

mod iterators_demo {    
	pub fn multiply( ){        
		// define a vector of 10 integers.        
		let nums = vec![1,2,3,4,5,6,7,8,9,10];
		// create an iterator to iterate over this list.        
		let nums_iterator =nums.iter();
	  }
}

Iterators in Rust allow us to loop through the list and perform operations / mutate the list if required. By default, immutable references are returned as values for iterators.

Additionally, iterators in rust are lazy by default. Just by defining an iterator variable, our code would have no effect. In fact, try compiling this and see what happens in this case. We need to perform operations on this iterator to start consuming the list.

warning: unused variable: `nums_iterator`  
--> src/lib.rs:9:13  
|  
9 |         let nums_iterator =nums.iter();  
|             ^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_nums_iterator`  
|  
= note: `#[warn(unused_variables)]` on by default

We know that vectors have their value stored on the Heap. Additionally, since iterators on vectors are implemented by standard library using Generics, we would get an immutable reference whenever we go through the list. We cannot modify the value / update the it the list when working with.iter() method. But rust provides a way for us to get a mutable iterator as well. Let’s look at how to use iterators and print the list using for loops.

For Loops on Vectors using Iterators

Let’s print our list before we dive into mutating the list. We create a new for loop using the for … insyntax. Try updating our function as shown below.

For Loops on Vectors using Iterators

Let’s print our list before we dive into mutating the list. We create a new for loop using the for … insyntax. Try updating our function as shown below.

pub fn multiply( ){  
      
    // define a vector of 10 integers.  
    let nums = vec![1,2,3,4,5,6,7,8,9,10]; // create an iterator to iterate over this list.  
        let nums_iterator =nums.iter(); for num in nums_iterator {  
            println!(" Num value {} " , num)  
        }  
    }warning: `map-iterator-demo` (lib test) generated 1 warning  
    Finished test [unoptimized + debuginfo] target(s) in 0.00s  
     Running unittests src/lib.rs (target/debug/deps/map_iterator_demo-bd3479416081810b)  
 Num value 1   
 Num value 2   
 Num value 3   
 Num value 4   
 Num value 5   
 Num value 6   
 Num value 7   
 Num value 8   
 Num value 9   
 Num value 10

Mutate Values using For Loops

We saw that iterators return references to the original elements in the list. We know what these references reside on the heap. So it’s possible to modify these values in place if we had a mutable reference to the elements.

This is where mutable iterators come into picture. In the example below we see how to modify list values in place using mutable iterators.

pub fn multiply( ){  
        // define a vector of 10 integers.  
        let mut nums = vec![1,2,3,4,5,6,7,8,9,10]; // create an iterator to iterate over this list.  
        let mut nums_iterator =nums.iter_mut(); for num in nums_iterator {  
            *num *= 12;  
        } for i in nums.iter() {  
            println!("New value {} " , i)  
        }  
    }

Notice the differences in the code. First, we make the vector mutable. Then we call the iter_mut() which gives a mutable reference to the element. And then we update the value to multiply it by 12. These steps are mandatory to update iterators

We then print the original list. Notice how each element now is updated to be multiplied by 12.

warning: `map-iterator-demo` (lib test) generated 2 warnings  
Finished test [unoptimized + debuginfo] target(s) in 0.00s  
Running unittests src/lib.rs (target/debug/deps/map_iterator_demo-bd3479416081810b)  
New value 12  
New value 24  
New value 36  
New value 48  
New value 60  
New value 72  
New value 84  
New value 96  
New value 108  
New value 120Process finished with exit code 0

Conclusion

In general mutability of an iterator also follows the same patterns as variable mutability. And the same holds for any complex data type. Iterators are powerful, and at some point, we may even need to create one in our programs by extending the Iterator trait in Rust. Their extensibility and performance allow us to write powerful programs in the simplest of ways.