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.