Polyglot
Languages Rust iterators
Rust § iterators

Iterators

The Iterator trait is one of Rust’s most distinctive abstractions. Any type implementing Iterator admits the substantial adapter and consumer surface of the standard library: map, filter, enumerate, zip, take, skip, collect, sum, count, fold, for_each, and so on. The principal property is laziness — adapters produce new iterators without doing work; the work happens at the consumer (collect, sum, for_each, etc.). The combination — explicit-iterator-based loops, lazy adapter chains, eager consumers, generic over the item type — admits substantial conciseness with zero runtime overhead (compile-time monomorphisation produces machine code equivalent to hand-written loops).

This page covers the iterator surface, the principal adapters and consumers, and the conventional patterns.

The Iterator trait

The trait, in essence:

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

    // ... many provided methods (map, filter, etc.) ...
}

The Item is an associated type; next() returns the next item or None when exhausted. The other methods (map, filter, etc.) have default implementations on top of next.

A simple iterator:

struct Counter { count: u32 }

impl Counter {
    fn new() -> Self { Counter { count: 0 } }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

let mut c = Counter::new();
println!("{:?}", c.next());                     // Some(1)
println!("{:?}", c.next());                     // Some(2)

Once Iterator is implemented, all the adapter and consumer methods become available:

let sum: u32 = Counter::new().sum();             // 15
let doubled: Vec<u32> = Counter::new().map(|n| n * 2).collect();

Producing iterators

The conventional sources:

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

v.iter();                                        // iter over &T
v.iter_mut();                                    // iter over &mut T
v.into_iter();                                   // iter over T (consumes)

(0..10);                                         // range
(0..=10);                                        // inclusive range

"hello".chars();                                 // iterator over chars
"hello".bytes();                                 // iterator over bytes
"hello world".split_whitespace();                // iterator over &str

std::iter::repeat(5);                            // infinite: 5, 5, 5, ...
std::iter::once(42);                             // single item
std::iter::empty::<i32>();                        // no items

The conventional discipline is to use iter() for borrowed iteration, iter_mut() for mutable iteration, into_iter() for consumption.

Adapters (lazy)

Adapters consume an iterator and produce another. They do no work until a consumer triggers iteration.

map

Transform each item:

let v = vec![1, 2, 3];
let doubled: Vec<i32> = v.iter().map(|&x| x * 2).collect();   // [2, 4, 6]

filter

Keep only items matching a predicate:

let v = vec![1, 2, 3, 4, 5];
let evens: Vec<i32> = v.iter().filter(|&&x| x % 2 == 0).copied().collect();  // [2, 4]

filter_map

Combine filter and map; the closure returns Option<U>:

let v = vec!["1", "two", "3", "four", "5"];
let nums: Vec<i32> = v.iter().filter_map(|s| s.parse().ok()).collect();  // [1, 3, 5]

enumerate

Pair each item with its index:

for (i, x) in v.iter().enumerate() {
    println!("[{}] = {}", i, x);
}

zip

Pair items from two iterators:

let a = vec![1, 2, 3];
let b = vec!["a", "b", "c"];
let pairs: Vec<(i32, &&str)> = a.iter().zip(b.iter()).collect();
// [(1, "a"), (2, "b"), (3, "c")]

The shorter iterator determines the length.

chain

Concatenate two iterators:

let a = vec![1, 2, 3];
let b = vec![4, 5, 6];
let combined: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
// [1, 2, 3, 4, 5, 6]

take and skip

let v = vec![1, 2, 3, 4, 5];
let first_three: Vec<i32> = v.iter().take(3).copied().collect();  // [1, 2, 3]
let after_two: Vec<i32> = v.iter().skip(2).copied().collect();    // [3, 4, 5]

take_while and skip_while

Conditional versions:

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

let prefix: Vec<i32> = v.iter().take_while(|&&x| x < 5).copied().collect();
// [1, 2, 3, 4]

let suffix: Vec<i32> = v.iter().skip_while(|&&x| x < 4).copied().collect();
// [4, 5, 4, 3]

step_by

Every nth item:

let v: Vec<i32> = (1..=10).step_by(2).collect();   // [1, 3, 5, 7, 9]

rev

Reverse the iterator (requires DoubleEndedIterator):

let v: Vec<i32> = (1..=5).rev().collect();        // [5, 4, 3, 2, 1]

flatten and flat_map

Flatten nested iterators:

let nested = vec![vec![1, 2], vec![3, 4], vec![5]];
let flat: Vec<i32> = nested.iter().flatten().copied().collect();
// [1, 2, 3, 4, 5]

// flat_map = map + flatten:
let words = vec!["hello", "world"];
let chars: Vec<char> = words.iter().flat_map(|s| s.chars()).collect();
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']

peekable

Admits looking at the next item without consuming:

let mut iter = vec![1, 2, 3].into_iter().peekable();

if let Some(&first) = iter.peek() {
    println!("first is {}", first);
}
println!("{:?}", iter.next());                   // Some(1)

inspect

Inspect items without modifying — useful for debugging:

let v: Vec<i32> = (1..=5)
    .inspect(|x| println!("before filter: {}", x))
    .filter(|&x| x % 2 == 0)
    .inspect(|x| println!("after filter: {}", x))
    .collect();

Other notable adapters

.cycle();                                        // repeat indefinitely
.fuse();                                         // None forever after the first None
.cloned();                                       // &T → T (requires Clone)
.copied();                                       // &T → T (requires Copy)
.windows(n);                                     // overlapping n-tuples (slices only)
.chunks(n);                                      // non-overlapping chunks (slices only)

Consumers (eager)

Consumers drive the iteration; they trigger the work that adapters described.

collect

Build a collection:

let v: Vec<i32> = (1..=10).collect();
let s: String = "hello".chars().rev().collect();
let m: HashMap<i32, i32> = (1..=10).map(|n| (n, n * n)).collect();
let set: HashSet<i32> = vec![1, 2, 2, 3, 3, 3].into_iter().collect();

The target type must implement FromIterator<T> for the iterator’s item type. The turbofish ::<> admits explicit type specification:

let v = (1..=10).collect::<Vec<i32>>();

sum, product

Aggregate to a single value:

let total: i32 = (1..=10).sum();                // 55
let factorial: i32 = (1..=5).product();         // 120

count, min, max

let n = v.iter().count();                        // length
let lo = v.iter().min();                         // Option<&T>
let hi = v.iter().max();                         // Option<&T>

min_by, max_by, min_by_key, max_by_key

let v = vec!["apple", "banana", "kiwi"];
let longest = v.iter().max_by_key(|s| s.len());  // Some(&"banana")
let shortest = v.iter().min_by_key(|s| s.len()); // Some(&"kiwi")

fold

Generic accumulation:

let sum = (1..=10).fold(0, |acc, x| acc + x);   // 55
let max = vec![3, 1, 4, 1, 5, 9].into_iter().fold(0, |a, b| a.max(b));

reduce

Like fold but uses the first item as the accumulator:

let max = vec![3, 1, 4, 1, 5, 9].into_iter().reduce(|a, b| a.max(b));
// Some(9)

Returns None for empty iterators.

any, all

Boolean tests:

let v = vec![1, 2, 3, 4, 5];
let has_even = v.iter().any(|&x| x % 2 == 0);   // true
let all_positive = v.iter().all(|&x| x > 0);    // true

find, position

Find an item:

let v = vec![1, 2, 3, 4, 5];
let first_even = v.iter().find(|&&x| x % 2 == 0);  // Some(&2)
let pos = v.iter().position(|&x| x == 3);          // Some(2)

for_each

Apply a closure to each item:

v.iter().for_each(|x| println!("{}", x));

The conventional Rust style favours for loops over for_each for printing and side effects:

for x in &v {
    println!("{}", x);
}

for_each is conventional in chains, where the for-loop equivalent is awkward.

last, nth

let last = (1..=10).last();                      // Some(10)
let third = (1..=10).nth(2);                     // Some(3)

Common patterns

Map and collect

let nums = vec![1, 2, 3, 4, 5];
let strings: Vec<String> = nums.iter().map(|n| n.to_string()).collect();

Filter and collect

let evens: Vec<i32> = (1..=20).filter(|n| n % 2 == 0).collect();

Map-filter chain

let result: Vec<i32> = (1..=100)
    .map(|n| n * n)
    .filter(|&n| n % 3 == 0)
    .take(10)
    .collect();

Sum a transform

let total: i32 = v.iter().map(|x| x * x).sum();

Group and count

use std::collections::HashMap;

fn count_occurrences<T: Eq + std::hash::Hash>(items: impl IntoIterator<Item = T>) -> HashMap<T, i32> {
    items.into_iter().fold(HashMap::new(), |mut map, item| {
        *map.entry(item).or_insert(0) += 1;
        map
    })
}

Find with predicate

let v = vec!["alice", "bob", "charlie"];
let starts_with_b = v.iter().find(|s| s.starts_with('b'));  // Some(&"bob")

Sort

The standard library’s sort is on the slice (not the iterator):

let mut v = vec![3, 1, 4, 1, 5];
v.sort();                                        // in place
println!("{:?}", v);                             // [1, 1, 3, 4, 5]

let mut v = vec!["banana", "apple", "cherry"];
v.sort_by_key(|s| s.len());

To sort while iterating, collect to a Vec and sort:

let mut sorted: Vec<i32> = (1..=10).collect();
sorted.sort();

Iterating in parallel (rayon)

The third-party rayon crate admits parallel iteration:

use rayon::prelude::*;

let v: Vec<i32> = (1..=1_000_000).collect();
let sum: i64 = v.par_iter().map(|&x| x as i64).sum();

The par_iter() produces a parallel iterator; the API mirrors Iterator.

Zip-and-sum

let xs = vec![1.0, 2.0, 3.0];
let ys = vec![4.0, 5.0, 6.0];

let dot: f64 = xs.iter().zip(ys.iter()).map(|(x, y)| x * y).sum();  // 32.0

Enumerate-and-pair

let words = vec!["zero", "one", "two", "three"];
let indexed: HashMap<usize, &&str> = words.iter().enumerate().collect();

A note on laziness

Adapters do no work until a consumer triggers iteration:

let v = vec![1, 2, 3];
let mapped = v.iter().map(|x| {
    println!("processing {}", x);
    x * 2
});
// nothing printed yet

let collected: Vec<i32> = mapped.collect();
// now "processing 1", "processing 2", "processing 3" prints

The mechanism admits substantial efficiency: chain operations build up a fused operation; the consumer drives it once. The compiler often inlines and optimises the entire chain, producing machine code equivalent to a hand-written loop.

A note on Iterator vs IntoIterator

Many APIs take IntoIterator rather than Iterator:

fn process<I>(iter: I) where I: IntoIterator<Item = i32> {
    for x in iter {
        // ...
    }
}

// Now any of these work:
process(vec![1, 2, 3]);
process(1..=10);
process([1, 2, 3]);

IntoIterator admits “anything that can be turned into an iterator”; Iterator is the iterator itself. The conventional API takes IntoIterator for substantial flexibility.

A note on the ? operator

The ? operator works in iterator chains via collect():

fn parse_all(strs: &[&str]) -> Result<Vec<i32>, std::num::ParseIntError> {
    strs.iter().map(|s| s.parse::<i32>()).collect()
}

The Result<Vec<T>, E> from-iterator instance: collects all Ok values; short-circuits on the first Err.

A note on the conventional discipline

The contemporary Rust iterator advice:

  • Prefer iterator chains over loops for transformation pipelines.
  • Use for loops for side effects; for_each only in chains.
  • Use collect::<Vec<_>>() (turbofish) when the target type isn’t obvious.
  • Use filter_map for combined filter-and-map.
  • Use flat_map for “map and flatten”.
  • Use enumerate for index-and-value iteration.
  • Use zip for parallel iteration.
  • Trust the compiler — the compiled chain is as fast as a hand-written loop.

The combination — lazy adapters, eager consumers, the substantial standard library, generic over IntoIterator — is one of Rust’s most distinctive features. The mechanism admits substantial functional-style code with no runtime overhead.