Rust Iterators

Iterator Basics

The Iterator trait
// The core trait — one required method
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

// Using iterators
let v = vec![1, 2, 3, 4, 5];
let mut iter = v.iter();
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));

Iterators are lazy — they produce values one at a time. Nothing happens until you consume the iterator.

Three Iterator Methods

iter, iter_mut, into_iter
let v = vec![String::from("a"), String::from("b")];

// .iter() — borrows: yields &T
for s in v.iter() {
    println!("{s}");  // s: &String
}
// v is still valid

// .iter_mut() — borrows mutably: yields &mut T
let mut v = vec![1, 2, 3];
for n in v.iter_mut() {
    *n *= 2;
}
// v is now [2, 4, 6]

// .into_iter() — consumes: yields T
let v = vec![String::from("a"), String::from("b")];
for s in v.into_iter() {
    println!("{s}");  // s: String (owned)
}
// v is consumed — no longer valid

for x in &v is shorthand for v.iter(). for x in &mut v is v.iter_mut(). for x in v is v.into_iter().

Adaptors (Lazy Transforms)

map, filter, enumerate, zip, chain, take, skip
let v = vec![1, 2, 3, 4, 5];

// map — transform each element
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
// [2, 4, 6, 8, 10]

// filter — keep elements matching a predicate
let evens: Vec<&i32> = v.iter().filter(|x| *x % 2 == 0).collect();
// [2, 4]

// chain — combine two iterators
let more = vec![6, 7];
let all: Vec<&i32> = v.iter().chain(more.iter()).collect();

// enumerate — add index
for (i, val) in v.iter().enumerate() {
    println!("{i}: {val}");
}

// zip — pair two iterators
let names = vec!["sw01", "sw02"];
let ips = vec!["10.50.1.10", "10.50.1.11"];
let hosts: Vec<_> = names.iter().zip(ips.iter()).collect();
// [("sw01", "10.50.1.10"), ("sw02", "10.50.1.11")]

// take and skip
let first_3: Vec<_> = v.iter().take(3).collect();
let after_2: Vec<_> = v.iter().skip(2).collect();

Adaptors return new iterators. They are lazy — nothing runs until a consumer method is called.

Consumers (Eager Evaluation)

collect, sum, fold, any, all, find, count
let v = vec![1, 2, 3, 4, 5];

// collect — gather into a collection
let squares: Vec<i32> = v.iter().map(|x| x * x).collect();

// sum
let total: i32 = v.iter().sum();  // 15

// fold — accumulate with initial value
let product = v.iter().fold(1, |acc, x| acc * x);  // 120

// any / all
let has_even = v.iter().any(|x| x % 2 == 0);   // true
let all_pos = v.iter().all(|x| *x > 0);          // true

// find — first matching element
let first_even = v.iter().find(|x| *x % 2 == 0);  // Some(&2)

// count
let n = v.iter().filter(|x| *x > 3).count();  // 2

Consumers drive the iterator chain and produce a final value.

Chaining Patterns

Real-world iterator chains
// Parse a comma-separated string into validated ports
let ports: Vec<u16> = "22,80,443,0,99999"
    .split(',')
    .filter_map(|s| s.parse::<u16>().ok())
    .filter(|&p| p > 0 && p <= 65535)
    .collect();
// [22, 80, 443]

// Process lines from a file
let active_hosts: Vec<String> = std::fs::read_to_string("/etc/hosts")?
    .lines()
    .filter(|line| !line.starts_with('#'))
    .filter(|line| !line.trim().is_empty())
    .map(|line| line.split_whitespace().nth(1).unwrap_or("").to_string())
    .filter(|name| !name.is_empty())
    .collect();

filter_map combines filter and map — it applies a function that returns Option and keeps only the Some values.

Implementing Iterator

Custom iterator
struct Countdown {
    value: u32,
}

impl Countdown {
    fn new(start: u32) -> Self {
        Countdown { value: start }
    }
}

impl Iterator for Countdown {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.value > 0 {
            self.value -= 1;
            Some(self.value + 1)
        } else {
            None
        }
    }
}

for n in Countdown::new(5) {
    print!("{n} ");  // 5 4 3 2 1
}

Implement next() and you get map, filter, collect, and all other adaptor/consumer methods for free.