[Avg. reading time: 14 minutes]
Iterator in Rust
What is Iteration?
Iteration means traversing a collection of values one element at a time.
Examples of collections include:
- Arrays
- Vectors
- HashMaps
- Slices
In Rust, iteration can be done using:
- Traditional loops
- Iterators
Both approaches allow you to process elements in a collection, but they follow different programming styles.
Loops vs Iterators
Loops – Imperative Style
Loops follow an imperative programming style.
In this approach you explicitly control how the loop operates.
You must manage:
- Index position
- Loop bounds
- Increment logic
Example
fn main() { let ages = [27, 35, 40, 10, 19]; let mut index = 0; while index < ages.len() { let age = ages[index]; println!("Age = {:?}", age); index += 1; } }
Here you manually:
- Track the position
- Check the array length
- Increment the counter
The following code will panic because the loop goes out of bounds.
fn main() { let ages = [27, 35, 40, 10, 19]; let mut index = 0; while index <= ages.len() { let age = ages[index]; println!("Age = {:?}", age); index += 1; } }
Iterators – Declarative Style
Iterators follow a declarative programming style. Instead of describing how to loop, you describe what should happen to each element. Rust handles the iteration logic internally.
Benefits
- Cleaner code
- Safer iteration
- Less chance of index errors
- Encourages functional programming patterns
fn main() {
let ages = [27, 35, 40, 10, 19];
let ages_iterator = ages.iter();
for age in ages_iterator {
println!("Age = {:?}", age);
}
}
Developer doesnt have to manage the
- Index
- Length
- Counter increment
Rust abstracts that away.
Iterator Trait
In Rust, iterators are based on a trait called Iterator.
Every iterator must implement the next() method.
fn main() { let ages = [27, 35, 40, 10, 19]; let mut ages_iterator = ages.iter(); println!("{:?}", ages_iterator.next()); println!("{:?}", ages_iterator.next()); println!("{:?}", ages_iterator.next()); println!("{:?}", ages_iterator.next()); println!("{:?}", ages_iterator.next()); println!("{:?}", ages_iterator.next()); }
Each call to next() moves the iterator forward.
Iterating using while let
fn main() { let ages = [27, 35, 40, 10, 19]; let mut ages_iterator = ages.iter(); while let Some(age) = ages_iterator.next() { println!("{:?}", age); } }
Creating Iterators
Rust collections provide several ways to create iterators.
iter()
Returns an iterator over references.
fn main() { let ages = vec![27, 35, 40]; for age in ages.iter() { println!("{}", age); } }
iter_mut()
Returns mutable references allowing values to be modified.
fn main() { let mut ages = vec![27, 35, 40]; for age in ages.iter_mut() { *age += 1; } println!("{:?}", ages); }
into_iter()
Consumes the collection and moves the values.
fn main() { let ages = vec![27, 35, 40]; for age in ages.into_iter() { println!("{}", age); } // ages cannot be used here because ownership moved }
Iterator Adapters
Iterator adapters transform iterators into new iterators.
Common adapters include:
- map
- filter
- enumerate
- take
- skip
fn main() { let ages = [27, 35, 40, 10, 19]; let adults: Vec<i32> = ages .iter() .copied() .filter(|age| *age >= 18) .map(|age| age * 2) .collect(); println!("{:?}", adults); }
Collection
↓
Iterator
↓
Filter
↓
Map
↓
Collect
Iterator Consumers
Consumers produce a final result from an iterator.
Common consumers include:
- collect()
- sum()
- count()
- fold()
- for_each()
fn main() { let numbers = [1, 2, 3, 4, 5]; let total: i32 = numbers.iter().sum(); let total_length: usize = numbers.iter().count(); println!("{},{}", total, total_length); }
Lazy Evaluation
Rust iterators use lazy evaluation.
Operations do not run immediately.
fn main() { let ages = [27, 35, 40]; let result = ages .iter() .map(|age| { println!("Processing {}", age); age * 2 }); println!("Iterator created but not consumed"); }
Using collect()
Execution begins once a consumer is used.
fn main() { let ages = [27, 35, 40]; let result: Vec<_> = ages .iter() .map(|age| { println!("Processing {}", age); age * 2 }) .collect(); println!("{:?}", result); }
Collection
↓
Iterator
↓
map()
↓
collect()
↓
Execution happens
enumerate()
enumerate() adds an index to each element of an iterator.
fn main() { let ages = [27, 35, 40, 10]; for (index, age) in ages.iter().enumerate() { println!("Index: {}, Age: {}", index, age); } }
ages
↓
iter()
↓
Iterator
↓
enumerate()
↓
(index, value)
take()
take(n) returns only the first n elements from an iterator.
fn main() { let numbers = [10, 20, 30, 40, 50]; for value in numbers.iter().take(3) { println!("{}", value); } }
numbers.iter()
↓
take(3)
↓
iterator stops after 3 elements
skip()
skip(n) ignores the first n elements and starts iteration after that.
fn main() { let numbers = [10, 20, 30, 40, 50]; for value in numbers.iter().skip(2) { println!("{}", value); } }
Use Cases
Iterators are widely used in systems and data processing.
Common use cases include:
- Stream log processing
- Network packet processing
- ETL style pipelines
- CLI tools
- Data transformation pipelines
numbers.iter()
↓
skip(2)
↓
iteration begins at element 3