tokio-rayon

Runs CPU-heavy Rayon computations from async Tokio code and awaits the result as a future.

Library
Cargo
v2.1.0
157stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
34/100Needs Attention
Development Activity0
Maintenance0
Community56
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
82/100Excellent
Architecture88
Code Quality90
Innovation85
Learning Curve65

tokio-rayon solves a specific friction point in async Rust: Tokio’s own spawn_blocking is built for blocking I/O, not CPU-bound work, so pairing it with Rayon’s fixed-size thread pool is a common pattern that normally requires hand-wiring channels. This crate does that wiring for you — it wraps Rayon’s spawn/spawn_fifo (both on the global pool and on a custom ThreadPool via an extension trait) in a tokio::sync::oneshot channel and returns an AsyncRayonHandle<T> that implements Future, so the result of the Rayon task can simply be .awaited from async code.

The API mirrors Rayon’s own naming (spawn, spawn_fifo) to minimize the learning curve for anyone already familiar with Rayon, and panics inside the spawned closure are caught and re-propagated through the returned future via std::panic::resume_unwind rather than silently triggering Rayon’s panic handler. The crate itself is intentionally tiny — four source files, a sealed trait, and no runtime dependencies beyond the sync feature of Tokio and Rayon itself.

What You Get

  • Free functions tokio_rayon::spawn and tokio_rayon::spawn_fifo that run a closure on Rayon’s global thread pool and return an awaitable handle
  • An AsyncThreadPool extension trait adding spawn_async/spawn_fifo_async methods directly onto Rayon’s ThreadPool, for use with a custom (non-global) pool
  • AsyncRayonHandle<T>, a Future implementation over a tokio::sync::oneshot::Receiver that resolves with the closure’s return value
  • Transparent panic propagation from the Rayon thread back through the awaited future via catch_unwind/resume_unwind, instead of triggering Rayon’s global panic handler
  • A re-exported rayon module so consumers don’t need to add Rayon as a separate direct dependency just to build thread pools

Common Use Cases

  • Offloading CPU-bound cryptographic, compression, or serialization work from a Tokio-based async web service without blocking the async executor
  • Running data-parallel Rayon computations (e.g. par_iter pipelines) triggered by an async request handler and awaiting the aggregated result
  • Isolating CPU-heavy work onto a dedicated, fixed-size Rayon ThreadPool (via the extension trait) separate from Tokio’s own worker threads, for predictable resource usage
  • Replacing hand-rolled tokio::sync::oneshot plumbing between Rayon callbacks and async code with a single .await

Under The Hood

Architecture tokio-rayon is organized into four small, single-purpose modules re-exported from lib.rs: global.rs exposes free-function wrappers (spawn, spawn_fifo) around Rayon’s own global-pool functions, async_thread_pool.rs defines a sealed AsyncThreadPool extension trait implemented only for rayon::ThreadPool (the private::Sealed pattern prevents downstream crates from implementing it themselves), and async_handle.rs defines AsyncRayonHandle<T>, the Future that bridges the two runtimes. Every entry point follows the same data flow: a tokio::sync::oneshot channel is created, the closure is handed to Rayon wrapped in catch_unwind, its result (or panic payload) is sent through the channel’s sender, and the returned AsyncRayonHandle polls the receiver, calling resume_unwind on a caught panic so it surfaces at the .await point rather than being swallowed. Because both public entry points funnel through the same handle type, changing that bridging logic is a single, well-contained change.

Tech Stack The crate depends on tokio with default-features = false and only the sync feature enabled — pulling in nothing but the oneshot-channel primitive rather than the full runtime — plus rayon for its thread pool. Dev-dependencies add tokio-test and the fuller macros/rt/time/rt-multi-thread Tokio features needed to drive #[tokio::test] async tests. There’s no web framework, database, or ORM involved; this is a pure runtime-integration library. Development tooling runs through cargo-make (Makefile.toml), with CI on GitHub Actions building and testing across stable, beta, nightly, and the pinned MSRV (1.45.0) in a matrix, cargo tarpaulin generating coverage uploaded to Codecov, and cargo-release handling publishing to crates.io.

Code Quality The crate opts into strict lint levels via #![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo, missing_docs)], meaning every public item must carry rustdoc, and CI enforces both clippy (with RUSTFLAGS=-D warnings) and rustfmt --check. Tests live alongside the code they cover in #[cfg(test)] mod tests blocks and use #[tokio::test] to exercise both the extension-trait and free-function entry points, verifying successful results, correct thread-pool placement (via rayon::current_thread_index()), and panic propagation through #[should_panic] assertions. There’s no untested edge case in the small public surface, and Rust’s type system rules out an entire class of bugs the crate would otherwise need runtime checks for.

API Design The public API deliberately mirrors Rayon’s own naming (spawn/spawn_fifo as free functions, spawn_async/spawn_fifo_async as trait methods) so anyone already familiar with Rayon needs to learn almost nothing new — call the closure, .await the handle. There’s no setup boilerplate: no builder, no configuration struct, just a direct wrapper. The one deliberate design constraint — sealing AsyncThreadPool so it can’t be implemented for other types — keeps the API surface predictable at the cost of external extensibility, a reasonable trade-off for a crate this narrowly scoped. Every public item is documented with rustdoc, including a runnable doctest in the crate root.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search