Guide contents

Lesson 40 of 94

Custom Iterators

Implementing Iterator for your own types gives them every adapter from the cookbook for free: map, filter, zip, sum, collect, and the rest. This lesson shows how, from a counter to lazy iterators over trees and paginated APIs.

Need Rust
Make a type an iterator impl Iterator for X with one method, next() -> Option<T>
Make for x in &coll work impl IntoIterator for &Coll
Iterate from a seed std::iter::successors(Some(seed), f)
Generate items from a closure std::iter::from_fn(...) or repeat_with(...)

1. Implementing Iterator

One associated type, Item, and one method, next.

Rust
struct Countdown {
    remaining: u32,
}

impl Iterator for Countdown {
    type Item = u32;

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

fn main() {
    for n in (Countdown { remaining: 3 }) {
        println!("{n}");
    }
}
Output
3
2
1

The parentheses are needed because a { right after for n in Countdown would otherwise be read as the start of the loop body.

2. Every adapter comes for free

Rust
struct Countdown {
    remaining: u32,
}

impl Iterator for Countdown {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if self.remaining == 0 {
            return None;
        }
        self.remaining -= 1;
        Some(self.remaining + 1)
    }
}

fn main() {
    let evens: Vec<u32> = Countdown { remaining: 10 }.filter(|n| n % 2 == 0).collect();
    let sum: u32 = Countdown { remaining: 100 }.sum();
    let pairs: Vec<(u32, char)> = Countdown { remaining: 3 }.zip("abc".chars()).collect();
    println!("{:?} {sum} {:?}", evens, pairs);
}
Output
[10, 8, 6, 4, 2] 5050 [(3, 'a'), (2, 'b'), (1, 'c')]

3. An infinite iterator: Fibonacci

An iterator that never returns None is fine, because adapters like take and take_while stop pulling.

Rust
struct Fibonacci {
    current: u64,
    next: u64,
}

impl Fibonacci {
    fn new() -> Self {
        Self { current: 0, next: 1 }
    }
}

impl Iterator for Fibonacci {
    type Item = u64;
    fn next(&mut self) -> Option<u64> {
        let value = self.current;
        self.current = self.next;
        self.next += value;
        Some(value)
    }
}

fn main() {
    let first_ten: Vec<u64> = Fibonacci::new().take(10).collect();
    println!("{:?}", first_ten);
    let first_over_1000 = Fibonacci::new().find(|&f| f > 1_000);
    println!("{:?}", first_over_1000);
    let even_sum: u64 = Fibonacci::new().take_while(|&f| f < 4_000_000).filter(|f| f % 2 == 0).sum();
    println!("sum of even terms below 4 million: {even_sum}");
}
Output
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Some(1597)
sum of even terms below 4 million: 4613732

4. An iterator that borrows a collection

An iterator over data owned by someone else holds a reference, so it needs a lifetime parameter (Lifetimes in Structs). The Item is a reference with that lifetime.

Rust
struct Song {
    title: &'static str,
    seconds: u32,
}

struct LongSongs<'a> {
    songs: &'a [Song],
    pos: usize,
    min_seconds: u32,
}

impl<'a> Iterator for LongSongs<'a> {
    type Item = &'a Song;

    fn next(&mut self) -> Option<&'a Song> {
        while self.pos < self.songs.len() {
            let song = &self.songs[self.pos];
            self.pos += 1;
            if song.seconds >= self.min_seconds {
                return Some(song);
            }
        }
        None
    }
}

fn main() {
    let playlist = vec![
        Song { title: "Intro", seconds: 45 },
        Song { title: "Epic", seconds: 540 },
        Song { title: "Single", seconds: 210 },
        Song { title: "Suite", seconds: 1_260 },
    ];
    let long = LongSongs { songs: &playlist, pos: 0, min_seconds: 300 };
    for s in long {
        println!("{} ({}:{:02})", s.title, s.seconds / 60, s.seconds % 60);
    }
}
Output
Epic (9:00)
Suite (21:00)

5. An iter() method on your collection

Collections conventionally offer iter(). It returns your iterator struct, borrowing self.

Rust
struct Inventory {
    items: Vec<(String, u32)>,
}

struct InStock<'a> {
    inner: std::slice::Iter<'a, (String, u32)>,
}

impl<'a> Iterator for InStock<'a> {
    type Item = &'a str;
    fn next(&mut self) -> Option<&'a str> {
        for (name, qty) in self.inner.by_ref() {
            if *qty > 0 {
                return Some(name);
            }
        }
        None
    }
}

impl Inventory {
    fn in_stock(&self) -> InStock<'_> {
        InStock { inner: self.items.iter() }
    }
}

fn main() {
    let inv = Inventory {
        items: vec![(String::from("apple"), 3), (String::from("pear"), 0), (String::from("kiwi"), 12)],
    };
    let names: Vec<&str> = inv.in_stock().collect();
    println!("{:?}", names);
    println!("{} products in stock", inv.in_stock().count());
}
Output
["apple", "kiwi"]
2 products in stock

Wrapping a standard iterator (std::slice::Iter) and filtering in next avoids index bookkeeping.

6. Returning impl Iterator is often simpler

Writing a struct is not always necessary. If the iteration can be expressed with existing adapters, return impl Iterator from a method instead.

Rust
struct Inventory {
    items: Vec<(String, u32)>,
}

impl Inventory {
    fn in_stock(&self) -> impl Iterator<Item = &str> + '_ {
        self.items.iter().filter(|(_, qty)| *qty > 0).map(|(name, _)| name.as_str())
    }

    fn total_units(&self) -> u32 {
        self.items.iter().map(|(_, q)| q).sum()
    }
}

fn main() {
    let inv = Inventory {
        items: vec![(String::from("apple"), 3), (String::from("pear"), 0), (String::from("kiwi"), 12)],
    };
    println!("{:?} {}", inv.in_stock().collect::<Vec<_>>(), inv.total_units());
}
Output
["apple", "kiwi"] 15

+ '_ says the returned iterator borrows from self. Write a struct when the iteration needs its own state that adapters can't express.

7. IntoIterator for &Collection: making for x in &coll work

for x in &playlist calls IntoIterator::into_iter(&playlist). Implementing it for a reference to your type makes your collection feel built in.

Rust
struct Playlist {
    songs: Vec<String>,
}

impl<'a> IntoIterator for &'a Playlist {
    type Item = &'a String;
    type IntoIter = std::slice::Iter<'a, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.songs.iter()
    }
}

fn main() {
    let p = Playlist { songs: vec![String::from("Intro"), String::from("Main Theme"), String::from("Outro")] };
    for song in &p {
        println!("{song}");
    }
    println!("still usable: {} songs", p.songs.len());
}
Output
Intro
Main Theme
Outro
still usable: 3 songs

8. IntoIterator for the owned collection and for &mut

Implement all three forms and your type behaves exactly like Vec in for loops.

Rust
#[derive(Debug)]
struct Batch {
    jobs: Vec<String>,
}

impl IntoIterator for Batch {
    type Item = String;
    type IntoIter = std::vec::IntoIter<String>;
    fn into_iter(self) -> Self::IntoIter {
        self.jobs.into_iter()
    }
}

impl<'a> IntoIterator for &'a mut Batch {
    type Item = &'a mut String;
    type IntoIter = std::slice::IterMut<'a, String>;
    fn into_iter(self) -> Self::IntoIter {
        self.jobs.iter_mut()
    }
}

fn main() {
    let mut batch = Batch { jobs: vec![String::from("resize"), String::from("encode")] };
    for job in &mut batch {
        job.push_str(" (queued)");
    }
    println!("{:?}", batch);
    for job in batch {
        println!("running {job}"); // owned String; `batch` is consumed
    }
}
Output
Batch { jobs: ["resize (queued)", "encode (queued)"] }
running resize (queued)
running encode (queued)

9. std::iter::from_fn: an iterator from a closure

from_fn builds an iterator from a closure that returns Option<T>. The closure's captured variables are the iterator's state, with no struct needed.

Rust
fn main() {
    let mut id = 1000;
    let ids = std::iter::from_fn(|| {
        id += 7;
        Some(format!("ORD-{id}"))
    });
    let batch: Vec<String> = ids.take(3).collect();
    println!("{:?}", batch);

    let text = "key1=a;key2=b;key3=c";
    let mut rest = text;
    let pairs: Vec<&str> = std::iter::from_fn(|| {
        if rest.is_empty() {
            return None;
        }
        let (head, tail) = rest.split_once(';').unwrap_or((rest, ""));
        rest = tail;
        Some(head)
    })
    .collect();
    println!("{:?}", pairs);
}
Output
["ORD-1007", "ORD-1014", "ORD-1021"]
["key1=a", "key2=b", "key3=c"]

10. std::iter::successors: each item computed from the previous

successors(first, f) yields first, then f(first), and so on, until f returns None.

Rust
fn main() {
    let powers_of_two: Vec<u32> = std::iter::successors(Some(1_u32), |&n| n.checked_mul(2)).take(10).collect();
    println!("{:?}", powers_of_two);

    let collatz: Vec<u64> = std::iter::successors(Some(6_u64), |&n| match n {
        1 => None,
        n if n % 2 == 0 => Some(n / 2),
        n => Some(3 * n + 1),
    })
    .collect();
    println!("{:?}", collatz);

    let backoff_ms: Vec<u64> = std::iter::successors(Some(100_u64), |&d| Some((d * 2).min(5_000))).take(8).collect();
    println!("{:?}", backoff_ms);
}
Output
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
[6, 3, 10, 5, 16, 8, 4, 2, 1]
[100, 200, 400, 800, 1600, 3200, 5000, 5000]

checked_mul returns None on overflow, which ends the sequence neatly.

11. repeat, repeat_with, once, and empty

Rust
use std::iter;

fn main() {
    let dashes: String = iter::repeat('-').take(10).collect();
    let mut n = 0;
    let squares: Vec<u32> = iter::repeat_with(|| {
        n += 1;
        n * n
    })
    .take(5)
    .collect();
    let with_header: Vec<&str> = iter::once("header").chain(["row1", "row2"]).collect();
    let nothing: Vec<u8> = iter::empty().collect();
    println!("{dashes} {:?} {:?} {:?}", squares, with_header, nothing);
}
Output
---------- [1, 4, 9, 16, 25] ["header", "row1", "row2"] []

12. DoubleEndedIterator: iterating from both ends

Implement next_back and your iterator supports rev(), last() efficiently, and meeting in the middle.

Rust
struct Range2 {
    start: i32,
    end: i32, // exclusive
}

impl Iterator for Range2 {
    type Item = i32;
    fn next(&mut self) -> Option<i32> {
        if self.start < self.end {
            self.start += 1;
            Some(self.start - 1)
        } else {
            None
        }
    }
}

impl DoubleEndedIterator for Range2 {
    fn next_back(&mut self) -> Option<i32> {
        if self.start < self.end {
            self.end -= 1;
            Some(self.end)
        } else {
            None
        }
    }
}

fn main() {
    println!("{:?}", Range2 { start: 1, end: 6 }.rev().collect::<Vec<_>>());
    let mut r = Range2 { start: 1, end: 6 };
    println!("{:?} {:?} {:?} {:?}", r.next(), r.next_back(), r.next(), r.collect::<Vec<_>>());
}
Output
[5, 4, 3, 2, 1]
Some(1) Some(5) Some(2) [3, 4]

13. size_hint and ExactSizeIterator

size_hint tells consumers how many items remain, which lets collect allocate the right capacity up front. When the count is exact, also implement ExactSizeIterator to get len().

Rust
struct Chunks {
    remaining: usize,
    chunk: usize,
}

impl Iterator for Chunks {
    type Item = usize;
    fn next(&mut self) -> Option<usize> {
        if self.remaining == 0 {
            return None;
        }
        let n = self.remaining.min(self.chunk);
        self.remaining -= n;
        Some(n)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let count = self.remaining.div_ceil(self.chunk);
        (count, Some(count))
    }
}

impl ExactSizeIterator for Chunks {}

fn main() {
    let upload = Chunks { remaining: 10_000, chunk: 4_096 };
    println!("{} chunks", upload.len());
    let sizes: Vec<usize> = upload.collect();
    println!("{:?} (capacity {})", sizes, sizes.capacity());
}
Output
3 chunks
[4096, 4096, 1808] (capacity 4)

14. An iterator that yields Results

Parsers often yield Result items, so the caller decides whether to stop at the first error or collect them all.

Rust
struct CsvRows<'a> {
    lines: std::str::Lines<'a>,
    line_no: usize,
}

impl<'a> Iterator for CsvRows<'a> {
    type Item = Result<(&'a str, u32), String>;

    fn next(&mut self) -> Option<Self::Item> {
        let line = self.lines.next()?;
        self.line_no += 1;
        let result = line
            .split_once(',')
            .ok_or(format!("line {}: missing comma", self.line_no))
            .and_then(|(name, qty)| {
                qty.trim().parse::<u32>().map(|q| (name.trim(), q)).map_err(|e| format!("line {}: {e}", self.line_no))
            });
        Some(result)
    }
}

fn main() {
    let data = "apple, 3\npear, x\nkiwi, 12\nbroken";
    let rows = CsvRows { lines: data.lines(), line_no: 0 };
    for r in rows {
        println!("{:?}", r);
    }
    let all: Result<Vec<_>, _> = CsvRows { lines: "a,1\nb,2".lines(), line_no: 0 }.collect();
    println!("{:?}", all);
}
Output
Ok(("apple", 3))
Err("line 2: invalid digit found in string")
Ok(("kiwi", 12))
Err("line 4: missing comma")
Ok([("a", 1), ("b", 2)])

let line = self.lines.next()?; uses ? on an Option inside a function returning Option, ending the iteration when the input runs out.

15. A custom adapter through an extension trait

Adapters are just structs wrapping another iterator. An extension trait with a blanket implementation adds your adapter as a method on every iterator.

Rust
struct DedupConsecutive<I: Iterator> {
    inner: std::iter::Peekable<I>,
}

impl<I> Iterator for DedupConsecutive<I>
where
    I: Iterator,
    I::Item: PartialEq,
{
    type Item = I::Item;

    fn next(&mut self) -> Option<I::Item> {
        let current = self.inner.next()?;
        while self.inner.peek() == Some(&current) {
            self.inner.next();
        }
        Some(current)
    }
}

trait DedupExt: Iterator + Sized {
    fn dedup_consecutive(self) -> DedupConsecutive<Self> {
        DedupConsecutive { inner: self.peekable() }
    }
}

impl<I: Iterator> DedupExt for I {}

fn main() {
    let readings = [1, 1, 2, 2, 2, 3, 1, 1];
    println!("{:?}", readings.iter().dedup_consecutive().collect::<Vec<_>>());
    let status: String = "aaabbbcccaa".chars().dedup_consecutive().collect();
    println!("{status}");
}
Output
[1, 2, 3, 1]
abca

This is how the popular itertools crate adds dozens of adapters to every iterator.

16. A tree traversal iterator

Walking a tree without recursion uses an explicit stack. The iterator yields nodes lazily, one per next call.

Rust
struct Node {
    name: &'static str,
    children: Vec<Node>,
}

struct DepthFirst<'a> {
    stack: Vec<(&'a Node, usize)>, // (node, depth)
}

impl<'a> Iterator for DepthFirst<'a> {
    type Item = (&'a str, usize);
    fn next(&mut self) -> Option<Self::Item> {
        let (node, depth) = self.stack.pop()?;
        for child in node.children.iter().rev() {
            self.stack.push((child, depth + 1));
        }
        Some((node.name, depth))
    }
}

impl Node {
    fn walk(&self) -> DepthFirst<'_> {
        DepthFirst { stack: vec![(self, 0)] }
    }
}

fn main() {
    let tree = Node {
        name: "src",
        children: vec![
            Node { name: "main.rs", children: vec![] },
            Node { name: "api", children: vec![Node { name: "users.rs", children: vec![] }, Node { name: "orders.rs", children: vec![] }] },
            Node { name: "lib.rs", children: vec![] },
        ],
    };
    for (name, depth) in tree.walk() {
        println!("{}{name}", "  ".repeat(depth));
    }
    let rust_files = tree.walk().filter(|(n, _)| n.ends_with(".rs")).count();
    println!("{rust_files} .rs files");
}
Output
src
  main.rs
  api
    users.rs
    orders.rs
  lib.rs
4 .rs files

17. Realistic example: a lazy paginated API client

The iterator fetches a new page only when the caller has used up the previous one. Stopping early (with take or find) avoids fetching the remaining pages entirely.

Rust
struct Api {
    total_users: u32,
}

impl Api {
    fn fetch_page(&self, page: u32, size: u32) -> Vec<String> {
        println!("  (HTTP GET /users?page={page}&size={size})");
        let start = page * size;
        (start..(start + size).min(self.total_users)).map(|i| format!("user-{i}")).collect()
    }
}

struct Users<'a> {
    api: &'a Api,
    page: u32,
    size: u32,
    buffer: std::vec::IntoIter<String>,
    done: bool,
}

impl<'a> Iterator for Users<'a> {
    type Item = String;
    fn next(&mut self) -> Option<String> {
        loop {
            if let Some(u) = self.buffer.next() {
                return Some(u);
            }
            if self.done {
                return None;
            }
            let page = self.api.fetch_page(self.page, self.size);
            self.page += 1;
            if page.len() < self.size as usize {
                self.done = true;
            }
            self.buffer = page.into_iter();
        }
    }
}

fn users(api: &Api, size: u32) -> Users<'_> {
    Users { api, page: 0, size, buffer: Vec::new().into_iter(), done: false }
}

fn main() {
    let api = Api { total_users: 7 };
    println!("first 4:");
    let first: Vec<String> = users(&api, 3).take(4).collect();
    println!("{:?}", first);
    println!("all:");
    println!("{} users", users(&api, 3).count());
}
Output
first 4:
  (HTTP GET /users?page=0&size=3)
  (HTTP GET /users?page=1&size=3)
["user-0", "user-1", "user-2", "user-3"]
all:
  (HTTP GET /users?page=0&size=3)
  (HTTP GET /users?page=1&size=3)
  (HTTP GET /users?page=2&size=3)
7 users

18. Realistic example: grouping multi-line records

A log where each record spans several lines, starting with a timestamp line. The iterator yields one complete record at a time.

Rust
struct Records<'a> {
    lines: std::iter::Peekable<std::str::Lines<'a>>,
}

impl<'a> Iterator for Records<'a> {
    type Item = Vec<&'a str>;
    fn next(&mut self) -> Option<Vec<&'a str>> {
        let header = self.lines.next()?;
        let mut record = vec![header];
        while let Some(line) = self.lines.peek() {
            if line.starts_with("20") {
                break; // the next record's timestamp line
            }
            record.push(self.lines.next().unwrap());
        }
        Some(record)
    }
}

fn main() {
    let log = "\
2026-09-27 10:00 ERROR payment failed
  at charge_card (payments.rs:42)
  at checkout (orders.rs:88)
2026-09-27 10:01 INFO retry scheduled
2026-09-27 10:05 ERROR timeout
  at call_bank (bank.rs:7)";
    for record in (Records { lines: log.lines().peekable() }) {
        println!("{} (+{} lines)", record[0], record.len() - 1);
    }
}
Output
2026-09-27 10:00 ERROR payment failed (+2 lines)
2026-09-27 10:01 INFO retry scheduled (+0 lines)
2026-09-27 10:05 ERROR timeout (+1 lines)

19. Realistic example: an iterator over a sliding time window

Given sorted event timestamps, yield the number of events in each fixed-size time bucket, including empty buckets.

Rust
struct Buckets<'a> {
    events: &'a [u64],
    pos: usize,
    bucket_start: u64,
    width: u64,
    end: u64,
}

impl<'a> Iterator for Buckets<'a> {
    type Item = (u64, usize);
    fn next(&mut self) -> Option<(u64, usize)> {
        if self.bucket_start >= self.end {
            return None;
        }
        let bucket_end = self.bucket_start + self.width;
        let mut count = 0;
        while self.pos < self.events.len() && self.events[self.pos] < bucket_end {
            count += 1;
            self.pos += 1;
        }
        let start = self.bucket_start;
        self.bucket_start = bucket_end;
        Some((start, count))
    }
}

fn main() {
    let events = [3, 7, 8, 21, 22, 23, 24, 41];
    let buckets = Buckets { events: &events, pos: 0, bucket_start: 0, width: 10, end: 50 };
    for (start, count) in buckets {
        println!("{start:>3}-{:<3} {}", start + 9, "*".repeat(count));
    }
}
Output
  0-9   ***
 10-19
 20-29  ****
 30-39
 40-49  *

20. Realistic example: a round-robin scheduler

Items from several queues, one at a time from each in turn, until every queue is empty.

Rust
struct RoundRobin<T> {
    queues: Vec<std::collections::VecDeque<T>>,
    next_queue: usize,
}

impl<T> Iterator for RoundRobin<T> {
    type Item = T;
    fn next(&mut self) -> Option<T> {
        for _ in 0..self.queues.len() {
            let q = self.next_queue;
            self.next_queue = (self.next_queue + 1) % self.queues.len();
            if let Some(item) = self.queues[q].pop_front() {
                return Some(item);
            }
        }
        None
    }
}

fn main() {
    let tenants = vec![
        vec!["a1", "a2", "a3", "a4"].into(),
        vec!["b1"].into(),
        vec!["c1", "c2"].into(),
    ];
    let schedule: Vec<&str> = RoundRobin { queues: tenants, next_queue: 0 }.collect();
    println!("{:?}", schedule);
}
Output
["a1", "b1", "c1", "a2", "c2", "a3", "a4"]

VecDeque is a double-ended queue, covered in VecDeque & BinaryHeap. Fair scheduling like this stops one busy tenant from starving the others.

21. Realistic example: generating dates

An iterator over calendar days between two dates, with correct month lengths and leap years.

Rust
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
struct Date {
    y: u32,
    m: u32,
    d: u32,
}

fn days_in_month(y: u32, m: u32) -> u32 {
    match m {
        2 if (y % 4 == 0 && y % 100 != 0) || y % 400 == 0 => 29,
        2 => 28,
        4 | 6 | 9 | 11 => 30,
        _ => 31,
    }
}

impl Date {
    fn succ(self) -> Date {
        if self.d < days_in_month(self.y, self.m) {
            Date { d: self.d + 1, ..self }
        } else if self.m < 12 {
            Date { m: self.m + 1, d: 1, ..self }
        } else {
            Date { y: self.y + 1, m: 1, d: 1 }
        }
    }
}

fn days_between(from: Date, to: Date) -> impl Iterator<Item = Date> {
    std::iter::successors(Some(from), |d| Some(d.succ())).take_while(move |d| *d <= to)
}

fn main() {
    let start = Date { y: 2024, m: 2, d: 27 };
    let end = Date { y: 2024, m: 3, d: 2 };
    for d in days_between(start, end) {
        println!("{}-{:02}-{:02}", d.y, d.m, d.d);
    }
    let nye = days_between(Date { y: 2025, m: 12, d: 30 }, Date { y: 2026, m: 1, d: 2 }).count();
    println!("{nye} days around new year");
}
Output
2024-02-27
2024-02-28
2024-02-29
2024-03-01
2024-03-02
4 days around new year

22. Realistic example: a lexer as an iterator

A tokenizer that implements Iterator<Item = Token> can be consumed lazily by a parser, or collected for debugging.

Rust
#[derive(Debug, PartialEq)]
enum Token<'a> {
    Num(f64),
    Ident(&'a str),
    Op(char),
}

struct Lexer<'a> {
    src: &'a str,
    pos: usize,
}

impl<'a> Iterator for Lexer<'a> {
    type Item = Token<'a>;
    fn next(&mut self) -> Option<Token<'a>> {
        let rest = &self.src[self.pos..];
        let trimmed = rest.trim_start();
        self.pos += rest.len() - trimmed.len();
        let c = trimmed.chars().next()?;
        let len = if c.is_ascii_digit() {
            trimmed.find(|ch: char| !(ch.is_ascii_digit() || ch == '.')).unwrap_or(trimmed.len())
        } else if c.is_alphabetic() {
            trimmed.find(|ch: char| !ch.is_alphanumeric()).unwrap_or(trimmed.len())
        } else {
            1
        };
        let text = &trimmed[..len];
        self.pos += len;
        Some(if c.is_ascii_digit() {
            Token::Num(text.parse().unwrap())
        } else if c.is_alphabetic() {
            Token::Ident(text)
        } else {
            Token::Op(c)
        })
    }
}

fn main() {
    let tokens: Vec<Token> = Lexer { src: "rate * 1.5 + base", pos: 0 }.collect();
    println!("{:?}", tokens);
    let idents = Lexer { src: "a + b * c - 42", pos: 0 }.filter(|t| matches!(t, Token::Ident(_))).count();
    println!("{idents} identifiers");
}
Output
[Ident("rate"), Op('*'), Num(1.5), Op('+'), Ident("base")]
3 identifiers

23. Realistic example: chaining several custom iterators

Custom iterators compose like built-in ones. Here, a date generator feeds a weekday filter and a formatter.

Rust
fn day_of_week(days_since_epoch: u64) -> &'static str {
    ["Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed"][(days_since_epoch % 7) as usize]
}

fn business_days(start_day: u64) -> impl Iterator<Item = (u64, &'static str)> {
    (start_day..).map(|d| (d, day_of_week(d))).filter(|(_, name)| *name != "Sat" && *name != "Sun")
}

fn main() {
    // 20_723 days after 1970-01-01 is 2026-09-27 (a Sunday)
    let next_five: Vec<String> = business_days(20_723).take(5).map(|(d, name)| format!("{name}#{d}")).collect();
    println!("{:?}", next_five);
    let sla_deadline = business_days(20_723).nth(9).unwrap();
    println!("10 business days later: day {} ({})", sla_deadline.0, sla_deadline.1);
}
Output
["Mon#20724", "Tue#20725", "Wed#20726", "Thu#20727", "Fri#20728"]
10 business days later: day 20735 (Fri)

24. Realistic example: primes with a stateful iterator

A prime generator that remembers the primes found so far and tests each candidate only against them.

Rust
struct Primes {
    found: Vec<u64>,
    candidate: u64,
}

impl Primes {
    fn new() -> Self {
        Self { found: Vec::new(), candidate: 1 }
    }
}

impl Iterator for Primes {
    type Item = u64;
    fn next(&mut self) -> Option<u64> {
        loop {
            self.candidate += 1;
            let c = self.candidate;
            if self.found.iter().take_while(|&&p| p * p <= c).all(|p| c % p != 0) {
                self.found.push(c);
                return Some(c);
            }
        }
    }
}

fn main() {
    println!("{:?}", Primes::new().take(15).collect::<Vec<_>>());
    println!("1000th prime: {:?}", Primes::new().nth(999));
    let twin_primes: Vec<(u64, u64)> = Primes::new()
        .zip(Primes::new().skip(1))
        .filter(|(a, b)| b - a == 2)
        .take(5)
        .collect();
    println!("{:?}", twin_primes);
}
Output
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
1000th prime: Some(7919)
[(3, 5), (5, 7), (11, 13), (17, 19), (29, 31)]

Summary

  • Implement Iterator with type Item and fn next(&mut self) -> Option<Item>, and every adapter works on your type.
  • Iterators over borrowed data carry a lifetime: struct Iter<'a> with type Item = &'a T.
  • Implement IntoIterator for &Coll, &mut Coll, and Coll so for loops work like they do for Vec.
  • For simple cases, return impl Iterator<Item = T> built from adapters, or use iter::from_fn and iter::successors.
  • Implement DoubleEndedIterator for rev(), and size_hint/ExactSizeIterator for accurate lengths.
  • Custom adapters are structs wrapping an iterator, exposed on every iterator through an extension trait.
  • Iterators are lazy, so they are ideal for paginated APIs, trees, and infinite sequences.

This completes the Closures & Iterators section. Next: Vec in Depth.