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:

  1. .iter() creates an iterator over the array
  2. .filter() keeps only elements greater than 5
  3. .map() doubles each remaining element
  4. .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.

Copyright (c) 2025 Ocean Softworks, Sharkk