Rust Iterators

This chapter introduces rust-iterators , a fundamental tool for processing sequences of data. Iterators provide a flexible, composable, and memory-safe way to perform operations like mapping, filtering, and collecting over collections.

Chapter Goals

  • Understand what iterators are and how they work in Rust.
  • Learn to create and use iterators effectively.
  • Explore common iterator adaptors and their functionality.
  • Discover best practices for working with iterators in Rust.

Key Characteristics of Rust Iterators

  • Lazy Evaluation: Iterators perform operations only when needed, making them efficient for large datasets.
  • Composable: Combine multiple iterator adaptors to create powerful processing pipelines.
  • Memory Safety: Iterators work seamlessly with Rust’s ownership model, ensuring safe access to data.
  • Reusable: Iterators can be consumed multiple times when created from a collection.

Basic Rules for Iterators

  1. Use the .iter() method to create an iterator from a collection.
  2. Combine iterators with adaptors for operations like mapping, filtering, and folding.
  3. Call .collect() to convert an iterator into a collection.
  4. Iterators are consumed by default; clone or recreate them if reuse is needed.
  5. Implement the Iterator trait for custom iteration logic.

Best Practices

  • Use iterator adaptors for concise and expressive code.
  • Avoid unnecessary clones; rely on references where possible.
  • Chain operations to process data efficiently.
  • Leverage .collect() for transforming results into collections.
  • Prefer iterator combinators over manual loops for clarity.

Syntax Table

Serial No Component Syntax Example Description
1 Creating an Iterator let iter = vec.iter(); Creates an iterator from a collection.
2 Mapping Values `let mapped = iter.map( x x * 2);` Transforms values in an iterator.
3 Filtering Values `let filtered = iter.filter( &x x > 2);` Filters values based on a condition.
4 Collecting Results let result: Vec<_> = iter.collect(); Converts an iterator into a collection.
5 Custom Iterator struct Counter; impl Iterator for Counter {} Implements a custom iterator.

Syntax Explanation

1. Creating an Iterator

What is Creating an Iterator? An iterator is a construct that allows sequential access to elements in a collection. In Rust, iterators can be created using .iter() or .into_iter() methods.

Syntax

let numbers = vec![1, 2, 3, 4, 5];

let iter = numbers.iter();

Detailed Explanation

  • .iter() creates an iterator over references to the elements of the collection.
  • The iterator does not consume the collection.

Example

for num in numbers.iter() {

    println!(“{}”, num);

}

Example Explanation

  • Iterates over numbers and prints each value.

2. Mapping Values

What is Mapping Values? Mapping applies a transformation function to each element in the iterator, creating a new iterator with transformed values.

Syntax

let doubled = iter.map(|x| x * 2);

Detailed Explanation

  • .map() takes a closure that defines the transformation.
  • Returns a new iterator without consuming the original.

Example

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

let doubled: Vec<_> = numbers.iter().map(|&x| x * 2).collect();

println!(“{:?}”, doubled);

Example Explanation

  • Multiplies each element in numbers by 2 and collects the results into a new vector.

3. Filtering Values

What is Filtering Values? Filtering removes elements from an iterator that do not satisfy a given condition.

Syntax

let filtered = iter.filter(|&x| x > 2);

Detailed Explanation

  • .filter() takes a closure that returns true for elements to keep.
  • Returns a new iterator with the filtered elements.

Example

let numbers = vec![1, 2, 3, 4];

let filtered: Vec<_> = numbers.iter().filter(|&&x| x > 2).collect();

println!(“{:?}”, filtered);

Example Explanation

  • Filters out numbers less than or equal to 2 and collects the remaining values.

4. Collecting Results

What is Collecting Results? The .collect() method consumes an iterator and gathers its elements into a collection.

Syntax

let result: Vec<_> = iter.collect();

Detailed Explanation

  • .collect() transforms an iterator into a specified collection type.
  • Supports collections like Vec, HashMap, or String.

Example

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

let collected: Vec<_> = numbers.into_iter().collect();

println!(“{:?}”, collected);

Example Explanation

  • Converts an iterator into a vector containing all the elements.

5. Custom Iterator

What is a Custom Iterator? A custom iterator defines its own logic for producing a sequence of values by implementing the Iterator trait.

Syntax

struct Counter {

    count: u32,

}

 

impl Counter {

    fn new() -> Counter {

        Counter { count: 0 }

    }

}

 

impl Iterator for Counter {

    type Item = u32;

 

    fn next(&mut self) -> Option<Self::Item> {

        self.count += 1;

        if self.count <= 5 {

            Some(self.count)

        } else {

            None

        }

    }

}

Detailed Explanation

  • next() defines the logic for producing the next value.
  • Returns Some(value) for valid elements and None to terminate iteration.

Example

let counter = Counter::new();

for value in counter {

    println!(“{}”, value);

}

Example Explanation

  • Produces values from 1 to 5 using the custom iterator.

Real-Life Project

Project Name: Grades Processor

Project Goal: Demonstrate iterator usage by processing student grades and calculating the average.

Code for This Project

fn main() {

    let grades = vec![85, 90, 78, 92, 88];




    let above_80: Vec<_> = grades.iter().filter(|&&grade| grade > 80).collect();

    let average: f32 = above_80.iter().copied().sum::<i32>() as f32 / above_80.len() as f32;




    println!("Grades above 80: {:?}", above_80);

    println!("Average of grades above 80: {:.2}", average);

}

Expected Output

Grades above 80: [85, 90, 92, 88]

Average of grades above 80: 88.75

Insights

  • Iterators enable concise and expressive data processing.
  • Lazy evaluation ensures efficient memory usage.
  • Combining iterator adaptors creates powerful transformation pipelines.

Key Takeaways

  • Use .iter() and iterator adaptors for streamlined data operations.
  • .collect() is essential for transforming iterators into collections.
  • Implement the Iterator trait for custom iteration logic.
  • Leverage composability for efficient and readable data processing.