Iterators in Rust programming
Nowadays Iterators are every programmer’s tool in hand.When I program array of objects/strings in C programming , I always wish that I could have some tool/function/operator where we can iterate through these objects or strings(words). “for” loop or “while” loop are going through array index in C programming .
If you are storing words in Array it become 2 dimensional array. So iterating through “words” become even more difficult in C programming .But it will be convenient if you can iterate through the types values/objects in the array. eg: Array(“I”,“like”,“rust”,“programming”) …if you can just iterate through the words rather than reading through the letters, it will be easy to make the words manipulations.
It is Functional programming style where we can just tell the program to iterate through the specified type .
Another use case would be eg: In the given Array (2,4,6,8) of elements if we want to find cube for all the elements in the array and then filter if cube is even number.
For conventional Loop structure we will be iterating through each element and find cube then check and filter if it is even. How great it would be if we can have something like below give us the same output number/maths calculations
example: array.iter().filter(x/2==0).collect()
eg2: words or sentence manipulation
array.iter().filter( words_contains_vowels)
It improves the readability of the program a lot. This functional programming style improves the readability ,but at the same time we can’t compromise the performance . Since they are as fast as our native loops , they are “Zero cost abstraction” in Rust ## iter from standard library The standard library std::iter provides Trait is Iterator. You want to build iterator any Struct ,
you can implement Iterator on your collection with below trait
trait Iterator { type Item fn next(&mut self) ->Option(Self::Item); }
In the below example, suppose if I have an input stream coming from outside and I want to iterator over word by word
//the structure which store the words and index of the words
//note :Since we are using String slice reference , the life time needs to provided explicitly
struct Mystream<'a>{
words: Vec<&'a str>,
index:usize
}
//implementing the structure with methods "new" and "next_word"
//"new" method create a structure object with words generated from stream
//next_word method calls next method in the Iterator trait
impl <'a>Mystream<'a>{
//"new" method get the string and split them into words vector
//index will be zero for object, but we will increment it in each iteration and use it as indexing the vector
fn new(stream:&'a str) -> Mystream{
Mystream{
words : stream.split(' ').collect(),
index:0,
}
}
//next_word method calls the "next" method from the iterator trait
fn next_word(&mut self) ->Option<&'a str>{
self.next()
}
}
//standard implementation for Iterator for "Mystream" struct
impl <'a>Iterator for Mystream<'a>{
type Item = &'a str;
fn next(&mut self) -> Option<&'a str>{
self.index +=1;
match Some(self.words[self.index]){
Some(word) => Some(word),
None => None,
}
}
}
fn main(){
let mut mystream = Mystream::new("I like to implement Rust Iterator for my stream");
let mut len = mystream.words.len();
println!("{:?},{}",mystream.words[0],len);
len = len -1;
while len > 0 {
println!("{:?},{}",mystream.next_word().unwrap(),len);
len = len -1;
}
}
Not sure above example is tough one, but let me try to explain it… Let assume we have a stream of words coming from input port, our task is to store it in a struct as words .(ie: split the sentence into words). I tried to explain most of the things in the comment section. But still we will go through it … The implementation of Iterator for “Mystream” struct is important part in the code. For understanding Iterator implementation you should know what is trait and struct creation and implementation of struct in rust
If look at the Iterator trait documentation, they ask us to create an associated type Item (Associated types are a way of associating a type placeholder with a trait such that the trait method definitions can use these placeholder types in their signatures.) and required method “next”
The next method passing parameter and return parameters are mentioned in the example.
As a general rule , for any Trait Rust documentation you will get the required methods or associated types needs to implemented for using it for your struct. Also we need to implement that Trait for our struct.
This case,
impl <'a>Iterator for Mystream<'a>
Since we are using vector of string slice(a reference type), we need to provide life time for it and struct.
Here we have given the associated type Item as &'a str (string slice) which means we are planning to iterate through string slice when the next method calls.
Return type for next method is Option <Self:Item> which means we need use match keyword to return Some(word) if there is value and None if nothing.
In the case Collection types: we can use the
Array,Vec, Hash etc with iter metod.
use std::collections::HashMap;
fn main(){
let arr = [1,2,3];
let v = vec![4,5,6];
let mut h = HashMap::new();
h.insert(1,"naveen");
h.insert(2,"davis");
h.insert(3,"tom");
println!("{:?}",arr.iter());
for i in arr.iter(){
println!("{}",i);
}
for i in v.iter(){
println!("{}",i);
}
for (i,name) in h.iter(){
println!("{:?}",name);
}
println!("{:?}",arr.iter().next()); //this is going print only first element
println!("{:?}",arr.iter().next()); //this is going print only first element
println!("{:?}",arr.iter().next()); //this is going print only first element
println!("{:?}",arr.iter().next()); //this is going print only first element
println!("{:?}",h.iter().next()); //this is going print only first element
println!("{:?}",h.iter().next()); //this is going print only first element
println!("{:?}",h.iter().next()); //this is going print only first element
println!("{:?}",h.iter().next()); //this is going print only first element
}
Iter
We can iterate through collection in 3 ways
- iter(), which iterates over
&T
. - iter_mut(), which iterates over
&mut T
. - into_iter(), which iterates over
T
.
iterate through immutable reference , in this case we are not supposed to change the value and but we using only reference to value
Rule one: any borrow must last for a scope no greater than that of the owner
Rule two: you may have EITHER 1+ immutable borrows OR EXACTLY 1 mutable borrow
What happens when we compile this code ?
If you have already gone through some Rust program before, you will shout immediately "compile time" error. As the vector moved in the first for loop and ownership has transfered, so when you try to use it again in the main the value was already dropped. We can use reference to the values to avoid this problem.
True . Rust won't allow to
fn main() {
let v = vec![3,4,5,7];
for i in v {
println!("{:?}",i);
}
for i in v {
println!("{}",i);
}
}
```rust_errors
use of moved value: `v`
--> src/main.rs:10:13
|
5 | for i in v {
| - value moved here
...
10 | for i in v {
| ^ value used here after move
In the below example , we solve above problem passing reference to for loop,
You can either use v.iter() or &v
fn main() {
let v = vec![3,4,5,7];
for i in &v {
println!("{:?}",i);
}
for i in v.iter() {
println!("{}",i);
}
println!("{:?}",v);
}
If you really want to pass the ownership to the for loop,
use either v or v.into_iter()
fn main() {
let v = vec![3,4,5,7];
for i in v.into_iter() {
println!("{}",i);
}
println!("{:?}",v);
}
```rust_errors
for i in v.into_iter() {
| - value moved here
...
11 | println!("{:?}",v);
| ^ value borrowed here after move
|
= note: move occurs because `v` has type `std::vec::Vec<i32>`
Another scenario is you want to update or change the value inside the for loop
you can either pass &mut v or you can use v.iter_mut()
fn main() {
let mut v = vec![3,4,5,7];
for i in &mut v {
*i +=1;
println!("{:?}",i);
}
println!("\nsecond iteration to change values\n");
for i in v.iter_mut() {
*i +=1;
println!("{:?}",i);
}
println!("{:?}",v);
}
IntoIterator
If you want to use ’ for’ loop in your type, you will need to build the trait IntoIterator for the type.
We will see how to implement an IntoIterator for our type.
Let build a ‘for’ loop for your Struct
How nice it would be if you can do a for loop which iterate through
[derive(Debug)]
struct Sentence{
words: Vec<String>,
}
impl IntoIterator for Sentence {
type Item = String;
type IntoIter = std::vec::IntoIter<std::string::String>;
fn into_iter(self) -> Self::IntoIter {
self.words.into_iter()
}
}
fn main() {
let sentence = Sentence {
words: vec!["one".to_string(), "two".to_string()]
};
for word in sentence{
println!("{}",word.contains("w"))
}
}
Standard Output
false
true
In the above example we are creating Sentence Struct which our type. In that we are storing words as Vector elements.
As we discussed in IntoIterator trait implementation, we need to implement the trait for our type Sentence
So two associated types are required ,
one is Item which is the item we are iterating through.
second is IntoIter ie in which type we are iterating through. here it is std::vec::IntoIterstd::string::String
Note: if it were a vector of integers , it would be std::vec::IntoIter
The required method for IntoIterator trait is into_iter which is also implemented with Rust document mentioned parameter and return type.