Iterators in Rust: map, filter, collect and other adapters
How Rust iterators work: the Iterator trait, lazy adapters like map, filter and enumerate, consumers like collect, sum and fold, and writing your own.
An iterator produces a sequence of values, one at a time. Anything implementing the Iterator trait has a next method that returns Some(item) until it's done, then None. for loops call next for you.
fn main() {
let orders = vec![120, 45, 300, 80, 15];
let big_total: i32 = orders
.iter()
.filter(|&&amount| amount >= 50)
.map(|amount| amount * 2)
.sum();
println!("{big_total}");
let labels: Vec<String> = orders.iter().enumerate().map(|(i, a)| format!("#{}={a}", i + 1)).collect();
println!("{labels:?}");
}
1000
["#1=120", "#2=45", "#3=300", "#4=80", "#5=15"]
Three ways to start
v.iter()yields&T(borrows),v.iter_mut()yields&mut T,v.into_iter()yieldsT(consumesv).- Ranges are iterators:
(1..=10),(0..n).rev(),(0..100).step_by(5). - Many methods return one:
s.chars(),s.lines(),s.split(','),map.keys(),slice.windows(2).
Adapters (lazy) and consumers (do the work)
Adapters take an iterator and return a new one. They're lazy: nothing runs until a consumer pulls values through. Calling v.iter().map(|x| x * 2); on its own does nothing, and rustc warns: "unused Map that must be used... iterators are lazy and do nothing unless consumed".
| Adapters | Consumers |
|---|---|
map, filter, filter_map |
collect, sum, product, count |
enumerate, zip, chain |
min, max, min_by_key, max_by |
take, skip, take_while, skip_while |
fold, reduce |
rev, step_by, peekable |
any, all, find, position |
flat_map, flatten, inspect |
last, nth, for_each, partition |
cloned, copied, windows/chunks (on slices) |
join (on Vec<String> after collect) |
collect needs a target type
collect can build a Vec, String, HashMap, HashSet, BTreeMap and more, so tell it which: let v: Vec<_> = ...collect(); or .collect::<Vec<_>>(). Without that you get E0283 or E0282, "type annotations needed". Two handy tricks:
- Collecting
(key, value)pairs builds a map:.collect::<HashMap<_, _>>(). - Collecting
Results intoResult<Vec<T>, E>stops at the first error:lines.map(|l| l.parse::<i32>()).collect::<Result<Vec<_>, _>>()?.
Zero-cost
Iterator chains compile down to the same machine code as a hand-written loop, often better, since bounds checks can be removed. Prefer them for clarity; there's no speed penalty in release builds.
Writing your own
Implement next, and every adapter above comes for free:
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<u32> { ... }
}
Common mistakes
- Collecting references when you wanted values: "a value of type
Vec<i32>cannot be built from an iterator over elements of type&i32" (E0277). Add.copied()or.cloned(). filterclosures get a reference to the item, so withiter()you see&&i32. Write|&&x| x > 5or|x| **x > 5, or use.copied()first.- Collecting just to iterate again.
v.iter().map(...).collect::<Vec<_>>().iter().sum()allocates for nothing; chain directly. sum()without a type: writelet total: i32 = ...sum();or.sum::<i32>().
Example
use std::collections::HashMap;
struct Countdown {
n: u32,
}
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.n == 0 {
return None;
}
self.n -= 1;
Some(self.n + 1)
}
}
fn main() {
let log = "INFO start\nWARN disk 91%\nERROR db down\nINFO retry\nERROR db down";
let errors: Vec<&str> = log.lines().filter(|l| l.starts_with("ERROR")).collect();
println!("{} errors, first: {:?}", errors.len(), errors.first());
let counts: HashMap<&str, usize> = log.lines().filter_map(|l| l.split_whitespace().next()).fold(HashMap::new(), |mut m, level| {
*m.entry(level).or_insert(0) += 1;
m
});
let mut levels: Vec<_> = counts.into_iter().collect();
levels.sort();
println!("{levels:?}");
let parsed: Result<Vec<i32>, _> = "4 8 15 16".split(' ').map(|s| s.parse::<i32>()).collect();
let broken: Result<Vec<i32>, _> = "4 x 15".split(' ').map(|s| s.parse::<i32>()).collect();
println!("{parsed:?} / {}", broken.is_err());
let squares: Vec<u64> = (1..=5).map(|n: u64| n * n).collect();
let (even, odd): (Vec<u64>, Vec<u64>) = squares.iter().partition(|&&n| n % 2 == 0);
println!("{squares:?} even {even:?} odd {odd:?}");
let launch: Vec<String> = Countdown { n: 3 }.map(|n| n.to_string()).chain(["liftoff".to_string()]).collect();
println!("{}", launch.join(" "));
let best = ["Ferris:31", "Corro:12", "Gopher:25"]
.iter()
.filter_map(|s| s.split_once(':'))
.max_by_key(|(_, score)| score.parse::<u32>().unwrap_or(0));
println!("{best:?}");
}
Output:
2 errors, first: Some("ERROR db down")
[("ERROR", 2), ("INFO", 2), ("WARN", 1)]
Ok([4, 8, 15, 16]) / true
[1, 4, 9, 16, 25] even [4, 16] odd [1, 9, 25]
3 2 1 liftoff
Some(("Ferris", "31"))