Iterators
Iterators let you chain operations on collections in a declarative, composable way. Instead of writing nested loops with mutable state, you describe what transformations you want and let the compiler figure out the rest.
Basic Iterator Chains
Here's the classic example—filter and map:
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$result = $numbers
.iter()
.filter(($x) { *$x > 5 })
.map(($x) { $x * 2 })
.collect();
// result: [12, 14, 16, 18, 20]
What's happening here:
.iter()creates an iterator over the array.filter()keeps only elements greater than 5.map()doubles each remaining element.collect()gathers results into a new array
Lazy Evaluation
Iterators are lazy—they don't do anything until you consume them. This is important for performance:
// This does nothing yet
$iterator = $huge_list
.iter()
.filter(($x) { expensive_check($x) })
.map(($x) { transform($x) });
// Work happens here
$first = $iterator.next();
No filtering or mapping happens until you call .next(), .collect(), or another consuming method. This means:
- You only process elements you actually need
- You can work with infinite sequences
- Intermediate collections aren't allocated
Common Operations
Transforming
// Map - transform each element
$doubled = $numbers.iter().map(($x) { $x * 2 }).collect();
// Flat map - transform and flatten
$words = $sentences.iter().flat_map(($s) { $s.split(" ") }).collect();
Filtering
// Keep elements that pass the test
$evens = $numbers.iter().filter(($x) { $x % 2 == 0 }).collect();
// Filter and transform in one step
$valid_scores = $items
.iter()
.filter_map(($item) {
if $item.is_valid() {
Some($item.score)
} else {
None
}
})
.collect();
Reducing
// Sum all elements
$total = $numbers.iter().sum();
// Product of all elements
$product = $numbers.iter().product();
// Custom reduction
$max = $numbers.iter().reduce(($a, $b) { if $a > $b { $a } else { $b } });
// Fold with initial value
$sum = $numbers.iter().fold(0, ($acc, $x) { $acc + $x });
Searching
// Find first match
$first_even = $numbers.iter().find(($x) { $x % 2 == 0 });
// Position of first match
$index = $numbers.iter().position(($x) { *$x == 5 });
// Check if any/all match
$has_negative = $numbers.iter().any(($x) { *$x < 0 });
$all_positive = $numbers.iter().all(($x) { *$x > 0 });
Taking and Skipping
// First n elements
$first_three = $numbers.iter().take(3).collect();
// Skip first n elements
$after_three = $numbers.iter().skip(3).collect();
// Take while condition holds
$before_five = $numbers.iter().take_while(($x) { *$x < 5 }).collect();
// Skip while condition holds
$from_five = $numbers.iter().skip_while(($x) { *$x < 5 }).collect();
Combining
// Zip two iterators together
$pairs = $names.iter().zip($scores.iter()).collect();
// [("Alice", 95), ("Bob", 87)]
// Chain iterators end to end
$all = $first.iter().chain($second.iter()).collect();
// Interleave
$mixed = $odds.iter().interleave($evens.iter()).collect();
Consuming Iterators
Iterators need to be consumed to produce results:
// Collect into a collection
$vec = $iter.collect::<Vec<_>>();
// Count elements
$count = $iter.count();
// Get last element
$last = $iter.last();
// For each (side effects only)
$iter.for_each(($x) { echo $x });
Working with Results
Iterator chains integrate smoothly with error handling:
// Collect Results - fails on first error
$results: !Vec<Data> = $urls
.iter()
.map(($url) { fetch($url) })
.collect();
// Filter out errors, keep successes
$successes = $results
.iter()
.filter_map(($r) { $r.ok() })
.collect();
// Partition into successes and failures
($successes, $failures) = $results
.iter()
.partition(($r) { $r.is_ok() });
Grouping and Partitioning
// Group by key
$by_status = $orders.iter().group_by(($o) { $o.status });
// { "pending": [...], "shipped": [...] }
// Partition by predicate
($passing, $failing) = $students
.iter()
.partition(($s) { $s.grade >= 60 });
// Chunk into fixed sizes
$batches = $items.iter().chunks(10).collect();
Creating Iterators
You can create iterators from various sources:
// From range
$nums = (0..100).iter();
// Generate with closure
$random = iter::from_fn(|| { Some(random()) });
// Repeat a value
$zeros = iter::repeat(0).take(10);
// Once
$single = iter::once(42);
// Empty
$nothing = iter::empty::<i32>();
Custom Iterators
Implement Iterator for your own types:
type Counter {
current i32
max i32
fn new($max i32): Counter {
return Counter { current: 0, max: $max };
}
@Iterator
type Item = i32;
@Iterator
fn next(&$this): ?i32 {
if $this.current < $this.max {
$value = $this.current;
$this.current += 1;
return Some($value);
}
return None;
}
}
// Use it
$counter = Counter::new(5);
for $i in $counter {
echo $i; // 0, 1, 2, 3, 4
}
Performance
Iterator chains are zero-cost abstractions. The compiler fuses them into a single pass:
// This:
$result = $numbers
.iter()
.map(($x) { $x * 2 })
.filter(($x) { *$x > 10 })
.sum();
// Compiles to essentially:
$result = 0;
for $x in $numbers {
$doubled = $x * 2;
if $doubled > 10 {
$result += $doubled;
}
}
No intermediate allocations, no extra passes over the data.
Parallel Iterators
For CPU-bound work, use parallel iterators:
$results = $items
.par_iter() // Parallel version
.map(($x) { expensive_computation($x) })
.collect();
Work is automatically distributed across threads. The same API, just with .par_iter() instead of .iter().
Common Patterns
Pipeline Processing
$report = $raw_data
.iter()
.filter(($r) { $r.is_valid() })
.map(($r) { parse($r) })
.filter(($p) { $p.date.year() == 2024 })
.group_by(($p) { $p.category })
.map(($k, $v) { ($k, $v.len()) })
.collect();
Windowing
// Sliding windows
$averages = $data
.iter()
.windows(3)
.map(($w) { $w.iter().sum() / 3 })
.collect();
Accumulating State
// Running total
$running_sum = $numbers
.iter()
.scan(0, ($state, $x) {
*$state += $x;
Some(*$state)
})
.collect();
// [1, 3, 6, 10, 15] for [1, 2, 3, 4, 5]
Flattening Nested Data
$all_items = $orders
.iter()
.flat_map(($o) { $o.items.iter() })
.collect();
Best Practices
Use iterators for data transformation. They're more readable than imperative loops with mutable state.
Don't over-chain. If a chain gets hard to read, break it into named steps:
$valid_items = $items.iter().filter(($x) { $x.is_valid() });
$transformed = $valid_items.map(($x) { transform($x) });
$result = $transformed.collect();
Prefer specific methods. Use .sum() instead of .fold(0, |a, b| a + b).
Remember laziness. Iterators are lazy—side effects in .map() won't happen until consumed.
Use parallel iterators for CPU-bound work. But profile first—parallelism has overhead.