Step 6 of 7
Returning iterators
A function that returns a Vec decides for the caller: it computes everything and allocates a new vector, even if the caller only wanted the first match, or a count, or to feed the items into another chain. Returning an iterator hands the caller a lazy sequence instead: nothing runs until they consume it, and they choose how.
struct User {
name: String,
active: bool,
score: u32,
}
struct Team {
users: Vec<User>,
}
impl Team {
fn active(&self) -> impl Iterator<Item = &User> {
self.users.iter().filter(|u| u.active)
}
fn names(&self) -> impl Iterator<Item = &str> {
self.users.iter().map(|u| u.name.as_str())
}
}
fn total(scores: impl IntoIterator<Item = u32>) -> u32 {
scores.into_iter().sum()
}
fn main() {
let team = Team {
users: vec![
User { name: String::from("ana"), active: true, score: 70 },
User { name: String::from("bo"), active: false, score: 95 },
User { name: String::from("cy"), active: true, score: 40 },
],
};
let strong: Vec<&str> = team.active().filter(|u| u.score > 50).map(|u| u.name.as_str()).collect();
println!("{strong:?}");
println!("{}", team.names().collect::<Vec<_>>().join(", "));
println!("{}", total(team.active().map(|u| u.score)));
println!("{}", total(vec![1, 2, 3]));
println!("{:?}", team.active().next().map(|u| &u.name));
}
["ana"]
ana, bo, cy
110
6
Some("ana")
Piece by piece:
-> impl Iterator<Item = &User>says "some iterator yielding&User", without naming its long real type (Filter<std::slice::Iter<'_, User>, {closure}>). The caller can chain more adapters onto it.- The returned iterator borrows
self. In edition 2024 that's automatic; in older code you'll see-> impl Iterator<Item = &User> + '_to say so explicitly. .next()on the result only does enough work to find the first active user: laziness means no wasted effort.- On the input side,
impl IntoIterator<Item = u32>accepts aVec, an array, a range or any iterator chain.
When branches return different iterators
Each adapter chain has its own type, so this doesn't compile:
fn pick(v: &[u32], only_even: bool) -> impl Iterator<Item = &u32> {
if only_even {
v.iter().filter(|x| *x % 2 == 0)
} else {
v.iter()
}
}
fn main() {
println!("{}", pick(&[1, 2, 3], true).count());
}
error[E0308]: `if` and `else` have incompatible types
impl Trait means one concrete type chosen by the function. When it truly varies, return a trait object: Box<dyn Iterator<Item = &u32> + '_>, with Box::new(...) around each branch. It costs one allocation and dynamic calls, which is usually fine.
Common mistakes
collect()inside the function, then.iter()outside: an allocation for nothing. Return the iterator and let the caller collect if they need to.- Returning an iterator over a local
Vec: theVecis dropped when the function returns, so a borrowing iterator can't outlive it (returns a value referencing data owned by the current function). Returnvec.into_iter(), which takes ownership, instead. - Forgetting it's lazy:
team.active().map(|u| println!("{}", u.name));prints nothing (and the compiler warnsunused Map that must be used). Use aforloop for side effects.
Your task
Give Log three methods that return iterators (no Vec in their return types): errors() yields the lines that start with ERROR; with_level(level) yields the message part of the lines that start with exactly that level followed by a space, so with_level("WARN") on WARN disk 91% yields disk 91% (and skips WARNING ...); numbered() yields (line_number, line) pairs counting from 1.
Challenge 2
Infinite iterators are fine when they're lazy. Write fn fibonacci() -> impl Iterator<Item = u64> (0, 1, 1, 2, 3, 5, ...) and fn multiples(n: u32) -> impl Iterator<Item = u32> (n, 2n, 3n, ... forever). The callers decide how many to take. std::iter::successors or a struct implementing Iterator both work.
Challenge 3
Return different iterators from different branches. Write fn select<'a>(items: &'a [Item], filter: Filter) -> Box<dyn Iterator<Item = &'a Item> + 'a> where Filter::All yields every item, Filter::InStock only items with qty > 0, and Filter::Category(name) only items whose category equals name.
Previous: RAII guards and Drop Next: Code review: from working to idiomatic