Iterators

Rust · Practical · 15 min

Iterator chains are the idiomatic way to process collections in Rust and compile to code as fast as hand-written loops.

An iterator is anything implementing trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }. Each call to next yields Some(item) until it returns None. Item is an associated type: each iterator has exactly one item type.

Collections give you three iterators: iter() yields &T, iter_mut() yields &mut T, and into_iter() yields owned T, consuming the collection. A for loop calls into_iter() on whatever you give it, which is why for x in &v yields references.

Adapters turn an iterator into another iterator: map, filter, filter_map, enumerate, zip, chain, take, skip, take_while, flat_map, rev, peekable, windows on slices. Adapters are lazy: nothing happens until a consumer pulls items. A map with no consumer does nothing (and the compiler warns).

Consumers drive the iteration: collect, sum, count, min/max, max_by_key, fold, any, all, find, position, for_each, last.

collect is generic over its output: it can build a Vec, String, HashMap, HashSet, and even a Result<Vec<T>, E> from an iterator of Results, stopping at the first error. Tell it what you want with a type annotation or turbofish: collect::<Vec<_>>().

Thanks to inlining, iterator chains usually compile to the same machine code as a manual loop, and they often avoid bounds checks. Prefer them for transforming data; use a plain for loop when the body has complex control flow or side effects.

To make your own type iterable, implement next; all adapters come with the trait. Implement IntoIterator for your collection so it works in for loops.

Example

struct Countdown(u32);

impl Iterator for Countdown {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if self.0 == 0 { return None; }
        self.0 -= 1;
        Some(self.0 + 1)
    }
}
fn main() {
    let words = ["apple", "bob", "kayak", "rust"];
    let palindromes: Vec<String> = words.iter()
        .filter(|w| w.chars().eq(w.chars().rev()))
        .map(|w| w.to_uppercase())
        .collect();
    println!("{palindromes:?}"); // ["BOB", "KAYAK"]

    let nums: Result<Vec<i32>, _> = "1,2,x".split(',').map(str::parse::<i32>).collect();
    println!("{}", nums.is_err()); // true: stops at the first error

    let total: u32 = Countdown(4).zip(1..).map(|(a, b)| a * b).sum();
    println!("{total}"); // 4*1 + 3*2 + 2*3 + 1*4 = 20
    println!("{:?}", words.iter().max_by_key(|w| w.len())); // Some("kayak"): last maximum wins
}

Remember

  • Implement next and you get every adapter for free
  • Adapters are lazy; consumers like collect, sum and for drive them
  • collect can build any FromIterator type, including Result<Vec<_>, E>

Watch out for

  • Calling .map(...) for side effects without a consumer, so nothing runs.

Check yourself

  1. What does `v.iter().map(|x| println!("{x}"));` print?
  2. Which call yields owned `String`s from a `Vec<String>` and consumes the vector?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: doc.rust-lang.org/book/ch13-02-iterators.html