Iterators¶
Rust iterators are lazy, composable, and zero-cost. The Iterator trait requires only one method (next), but provides dozens of adaptors (map, filter, fold, etc.) for free. Iterators compile to the same machine code as hand-written loops thanks to monomorphization - no runtime overhead.
Key Facts¶
Iteratortrait:fn next(&mut self) -> Option<Self::Item>- returnsNonewhen exhausted- Iterators are lazy - no computation until a consuming method (collect, sum, for_each) is called
- Three ways to iterate:
&v/.iter()(references),&mut v/.iter_mut()(mutable refs),v/.into_iter()(ownership) IntoIteratortrait enablesfor x in collectionsyntax- Custom iterators get all adaptor methods for free by implementing
Iterator collect()can build any collection that implementsFromIterator(Vec, HashMap, HashSet, String, etc.)
Patterns¶
Three Iteration Modes¶
let mut v = vec![1, 2, 3];
for x in &v { } // immutable references (&i32)
for x in &mut v { } // mutable references (&mut i32)
for x in v { } // takes ownership, v consumed
Key Adaptor Methods¶
let v = vec![1, 2, 3, 4, 5];
// Transform
v.iter().map(|x| x * 2) // [2, 4, 6, 8, 10]
// Filter
v.iter().filter(|&&x| x > 2) // [3, 4, 5]
// Fold (reduce with initial value)
v.iter().fold(0, |acc, x| acc + x) // 15
v.iter().sum::<i32>() // 15
// Chain and zip
let a = [1, 2];
let b = [3, 4];
a.iter().chain(b.iter()) // [1, 2, 3, 4]
a.iter().zip(b.iter()) // [(1,3), (2,4)]
// Take and skip
v.iter().take(3) // [1, 2, 3]
v.iter().skip(2) // [3, 4, 5]
// Enumerate
v.iter().enumerate() // [(0,1), (1,2), ...]
// Flatten nested iterators
vec![vec![1, 2], vec![3, 4]].into_iter().flatten() // [1, 2, 3, 4]
// flat_map = map + flatten
v.iter().flat_map(|&x| vec![x, x * 10])
// Search
v.iter().find(|&&x| x > 3) // Some(&4)
v.iter().any(|&x| x > 4) // true
v.iter().all(|&x| x > 0) // true
Collect into Various Types¶
let v = vec![1, 2, 3, 4, 5];
let set: HashSet<_> = v.iter().collect();
let map: HashMap<_, _> = v.iter().enumerate().collect();
let s: String = vec!['h', 'e', 'l', 'l', 'o'].into_iter().collect();
Lazy Evaluation¶
// Creates an iterator chain but does NOTHING
let lazy = v.iter().map(|x| {
println!("processing"); // never printed!
x * 2
});
// Only when consumed does it execute
let result: Vec<_> = lazy.collect(); // now prints and computes
Custom Iterators¶
struct Counter { count: u32, max: u32 }
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
// All adaptor methods available for free
Counter { count: 0, max: 5 }
.filter(|x| x % 2 == 0)
.sum::<u32>() // 6 (2 + 4)
IntoIterator Trait¶
// Enables `for x in my_collection`
impl IntoIterator for MyCollection {
type Item = MyItem;
type IntoIter = MyIterator;
fn into_iter(self) -> MyIterator { /* ... */ }
}
Gotchas¶
.iter()returns references;.into_iter()consumes the collection - mixing them up causes borrow errors- Chained iterators do nothing without a terminal operation (collect, sum, for_each, count, etc.)
collect()needs a type hint - use turbofish (::<Vec<_>>) or let-binding with type annotations.len()on String returns byte count, not char count - uses.chars().count()for characters
See Also¶
- [[closures]] - closures as arguments to iterator adaptors
- [[collections]] - Vec, HashMap, and their iteration interfaces
- [[traits]] - Iterator trait and associated types