Synchronous data-parallel primitives — Moirai's rayon-replacement surface.
This crate is the parallel domain (throughput over data), distinct from
the concurrent domain (moirai-async, async tasks/IO). All operations
here are fully synchronous (no async, no .await), so they are safe inside
pure compute kernels without async contagion, and operate on borrowed slices
with in-place mutation (zero-copy).
Selecting an execution strategy
Strategy is a zero-sized [ExecutionPolicy] type ([Sequential],
[Parallel], [Adaptive]) chosen at compile time, so every form below
monomorphizes with no dynamic dispatch:
- Extension traits (the surface) —
slice.par()/slice.par_mut()return [Adaptive] handles, thenfor_each/enumerate/map_collect/map_reduce:use ; let v: = .collect; let sum = v.par.map_reduce; // auto-routes let mut m = v.clone; m.par_mut.for_each; *_with::<P>free functions — a low-level override that pins the policy via turbofish (for_each_with::<Sequential>(&data, f)), for the rare case that needs to force sequential (determinism / nested regions) or parallel. Most code should just use.par().
Because [Adaptive] is itself a zero-sized policy, .par() is a fully
monomorphized, zero-cost abstraction that parallelizes only at or above
[ADAPTIVE_PARALLEL_THRESHOLD] and runs sequentially below it — the
parallel/sequential decision is automatic, with nothing to designate.
These data-parallel ops are synchronous (they return values, not futures),
but they run on the same unified hybrid scheduler as async work
([moirai_executor::global]) — not a separate pool. A .par() worker task
can therefore spawn or drive async work (moirai::global().spawn_async/
block_on) on that same runtime, so parallel processing and asynchronous
tasks compose within one process. The sync return shape here is a property of
the operation, not an isolation boundary.