Rust's borrow checker prevents most data races at the value level. But not the ones that matter when you try to parallelize a Redux-style reducer pipeline across independent pieces of state. Corentin Corgié set out to prove the compiler could refuse to build a parallel root reducer where two reducers might write to the same slice. He succeeded, and the journey is worth reading even if you never touch a frontend framework.
Why parallel reducers demand compile-time enforcement
At Corentin's day job, he works on energy-management systems running on industrial sites. The control layer uses a Redux-like pattern in Python: multiple concurrent controllers share a consistent view of plant state. A typical site has dozens of devices, each polled on its own period, some as tight as 50ms. That's a high-volume event stream. Python's sequential reducer pipeline struggled, so they cached reads and reduced at a coarser frequency. The tradeoff lost the exact timing guarantees that make Redux attractive.
Profiling pointed at the reducer phase. The plant has independent subsystems - solar, battery, meter, grid controller. Each subsystem holds a slice of the global state and its own reducer that only touches its slice. The reducers are independent by construction: the Solar reducer never writes to the Battery slice. Run them sequentially and you pay N times the latency. Run them in parallel and you get max(latency_i). The catch: parallel execution plus shared state equals data races the instant a reducer accidentally reaches into a neighbor's slice.
C++ hands you mutexes and hopes for the best. Higher-level languages give you a memory model but the burden stays on the developer. Corentin realized Rust's type system could encode the property directly. The compiler itself could refuse to build code that would race.
Slices, disjointness, and the first wrong idea
Two concepts drive the solution. A slice is a sub-field of the state - say counter, user, notifications. A slice reducer is a reducer whose function signature only exposes its slice; the type system literally forbids touching anything else. The property we want is disjointness: for any pair of slice reducers, they target different slices. No two reducers ever touch the same slice. Disjointness is necessary and sufficient for safe parallel execution.
Corentin's first attempt, called AllDistinct, came from his C++ background where this kind of check is template metaprogramming bread and butter. The idea: collect all slice types used by the reducers and assert at compile time that every pair is distinct. It almost worked but hit a wall with Rust's type-level list processing. The borrow checker wasn't the problem - the type system's ability to compare arbitrary types at compile time was the limiting factor. He doesn't give the full failure postmortem in this post, but the pivot is clear.
What the compiler accepted instead
The eventual ruxe design uses a different trick: instead of checking disjointness as a property of the reducer list, it encodes the per-reducer slice assignment in the type of each reducer, then lets the borrow checker enforce that two reducers given to a parallel combinator have incompatible types if they target the same slice. The details rely on Rust's trait system and marker types. The result: if you accidentally wire two reducers to the same slice in a parallel pipeline, the compiler spits out a type error that reads like "expected SliceMarker<Battery>, found SliceMarker<Meter>" - or whatever concrete slices you have.
That error gets raised at module build time, not at 3 AM in production. For teams building real-time control systems, or any high-throughput state machine with independent substate, that's the difference between a deploy you trust and a deploy you pray over.
Source: A data race that doesn't compile (in Rust)
Domain: corentin-core.github.io
Comments load interactively on the live page.