# Parallel Iterators

> ForkUnion for Zig

For Rayon-style ergonomics, use the parallel iterator API with the `prelude`.
Unlike Rayon, ForkUnion's parallel iterators don't depend on the global state and allow explicit control over the thread pool and scheduling strategy.
For statically shaped workloads, the default static scheduling is more efficient:

```rust
use forkunion as fu;
use forkunion::prelude::*;

let topology = fu::Topology::new().expect("Failed to detect hardware topology");
let mut pool = fu::spawn(&topology, 4);
let mut data: Vec<usize> = (0..1000).collect();

(&data[..])
    .into_par_iter()
    .with_pool(&mut pool)
    .for_each(|value, _task, _at| {
        println!("Value: {}", value);
    })?;
```

For dynamic work-stealing, use `with_schedule` with `DynamicScheduler`:

```rust
(&mut data[..])
    .into_par_iter()
    .with_schedule(&mut pool, DynamicScheduler)
    .for_each(|value, _task, _at| {
        *value *= 2;
    })?;
```

This easily composes with other iterator adaptors, like `map`, `filter`, and `zip`:

```rust
(&data[..])
    .into_par_iter()
    .filter(|&x| x % 2 == 0)
    .map(|x| x * x)
    .with_pool(&mut pool)
    .for_each(|value, _task, _at| {
        println!("Squared even: {}", value);
    })?;
```

For parallel reductions, ForkUnion provides Rayon-like convenience methods with automatic cache-aligned scratch allocation:

```rust
let data: Vec<u64> = (0..1_000_000).map(|i| i as u64).collect();

// Sum all elements with automatic scratch allocation
let total: u64 = (&data[..])
    .into_par_iter()
    .with_pool(&mut pool)
    .sum()?;

// Count elements matching a predicate
let evens = (&data[..])
    .into_par_iter()
    .filter(|&x| x % 2 == 0)
    .with_pool(&mut pool)
    .count()?;

// Custom reduction (maximum)
let maximum = (&data[..])
    .into_par_iter()
    .with_pool(&mut pool)
    .reduce(
        || 0u64,                                     // initial value
        |acc, value, _, _| *acc = (*acc).max(*value), // fold function
        |a, b| a.max(b)                              // combine function
    )?;
```

For manual control over scratch allocation, use `reduce_with_scratch`:

```rust
// Cache-line aligned wrapper to prevent false sharing
let mut scratch: Vec<CacheAligned<u64>> =
    (0..pool.threads_count()).map(|_| CacheAligned(0)).collect();

let total = (&data[..])
    .into_par_iter()
    .with_pool(&mut pool)
    .reduce_with_scratch(
        scratch.as_mut_slice(),
        |acc, value, _, _| acc.0 += *value,  // fold
        |a, b| a.0 += b.0                 // combine in-place
    )?;
```

Beyond reductions, the iterators offer short-circuiting searches - `find_first` and `find_last` for the deterministic lowest- or highest-index match, `find_any` for whichever match is found first with cooperative cancellation, and `any` / `all` for boolean predicates that stop the moment the answer is known.
Fallible bodies get `for_each_fallible` and `fold_with_scratch_fallible`, which propagate the first error and signal the other workers to stop.
Their two answers stay apart: the outer `Result` reports whether the dispatch ran, the inner one whether the caller's own work succeeded, so the caller's error type carries no constraint.
This Rayon-style layer is __Rust-only__; C, C++, Zig, and Mojo expose the pool primitives `for_threads`, `for_n`, `for_n_dynamic`, and `for_slices` directly.
Zig additionally mirrors Rust's `for_slices_mut` as `forSlicesMut`, which hands each thread a disjoint sub-slice rather than the `TasksRange` its indices would form.

`const forkunion = @import("forkunion");`

## CacheAligned

Wraps a value in cache-line-aligned, cache-line-padded storage so per-thread accumulators never share a cache line and thus never false-share.

```zig
pub fn CacheAligned(comptime T: type) type
```

Mirrors the C++ `cache_aligned` and the Rust `CacheAligned<T>`: allocate one per thread as
scratch, then combine after the parallel region.

- `value` `T align(default_alignment)`

