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.
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}");
}
}3
2
1The 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
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);
}[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.
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}");
}[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Some(1597)
sum of even terms below 4 million: 46137324. 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.
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);
}
}Epic (9:00)
Suite (21:00)5. An iter() method on your collection
Collections conventionally offer iter(). It returns your iterator struct, borrowing self.
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());
}["apple", "kiwi"]
2 products in stockWrapping 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.
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());
}["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.
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());
}Intro
Main Theme
Outro
still usable: 3 songs8. IntoIterator for the owned collection and for &mut
Implement all three forms and your type behaves exactly like Vec in for loops.
#[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
}
}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.
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);
}["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.
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);
}[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
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);
}---------- [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.
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<_>>());
}[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().
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());
}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.
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);
}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.
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(¤t) {
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}");
}[1, 2, 3, 1]
abcaThis 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.
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");
}src
main.rs
api
users.rs
orders.rs
lib.rs
4 .rs files17. 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.
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());
}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 users18. 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.
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);
}
}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.
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));
}
} 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.
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);
}["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.
#[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");
}2024-02-27
2024-02-28
2024-02-29
2024-03-01
2024-03-02
4 days around new year22. 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.
#[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");
}[Ident("rate"), Op('*'), Num(1.5), Op('+'), Ident("base")]
3 identifiers23. Realistic example: chaining several custom iterators
Custom iterators compose like built-in ones. Here, a date generator feeds a weekday filter and a formatter.
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);
}["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.
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);
}[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
Iteratorwithtype Itemandfn next(&mut self) -> Option<Item>, and every adapter works on your type. - Iterators over borrowed data carry a lifetime:
struct Iter<'a>withtype Item = &'a T. - Implement
IntoIteratorfor&Coll,&mut Coll, andCollsoforloops work like they do forVec. - For simple cases, return
impl Iterator<Item = T>built from adapters, or useiter::from_fnanditer::successors. - Implement
DoubleEndedIteratorforrev(), andsize_hint/ExactSizeIteratorfor 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.
