oneshot

A lock-free Rust spsc channel built to send exactly one message, seamlessly, between sync threads and async tasks.

Library
Cargo
v0.2.1
101stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
38/100Needs Attention
Development Activity4
Maintenance20
Community48
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
85/100Excellent
Architecture90
Code Quality96
Innovation88
Learning Curve65

oneshot is a Rust crate providing a single-producer, single-consumer channel purpose-built for one-time message handoff. Unlike a general-purpose mpsc channel, it bakes the one-message guarantee into the type system: Sender::send consumes self, so it is a compile-time error, not a runtime panic, to try sending twice. The Receiver supports both thread-blocking recv/recv_timeout and .await-based asynchronous receiving via IntoFuture, using the same underlying channel object for either mode.

Internally, each channel is a single heap allocation holding an atomic state byte, uninitialized storage for the message, and uninitialized storage for a waker, coordinated through a hand-written lock-free state machine rather than a mutex. The std and async features are both opt-in and off by default, so a minimal no_std build only supports non-blocking try_recv. The crate’s correctness is checked in CI under both Miri and the loom exhaustive-interleaving model, in addition to the normal test suite across the full feature powerset.

What You Get

  • A channel() function returning a Sender<T>/Receiver<T> pair backed by one heap allocation
  • Lock-free, largely wait-free Sender::send, with is_closed() to check for a dropped receiver
  • Non-blocking try_recv, thread-blocking recv/recv_timeout (via the std feature), and .await-based async receiving (via the async feature)
  • Typed errors (SendError, RecvError, TryRecvError, RecvTimeoutError) instead of panics
  • no_std compatibility when no optional features are enabled
  • Sender::into_raw/from_raw for passing a channel handle across an FFI boundary

Common Use Cases

  • Replying to a request routed through an mpsc worker queue, regardless of whether the caller is sync or async
  • Awaiting a reply from another task or thread inside an async function without depending on a specific executor
  • Handing a result computed on a dedicated blocking thread back to an async caller
  • Bounding how long a caller waits for a response with recv_timeout before treating the call as failed
  • Completing a Rust future from a C callback using into_raw/from_raw across an FFI boundary

Under The Hood

Architecture The crate is a single flat module tree (lib.rs, channel.rs, sender.rs, receiver.rs, states.rs, waker.rs, errors.rs) built around one shared heap allocation defined in channel.rs: a Channel<T> holding an AtomicU8 state, an UnsafeCell<MaybeUninit<T>> message slot, and an UnsafeCell<MaybeUninit<ReceiverWaker>> waker slot. Sender<T> and Receiver<T> are thin NonNull<Channel<T>> wrappers whose methods drive a hand-rolled state machine (states.rs: EMPTY/MESSAGE/RECEIVING/UNPARKING/DISCONNECTED) through compare_exchange/fetch_add/fetch_xor with carefully chosen memory orderings, so coordination is entirely lock-free atomics plus a brief busy-wait window during the UNPARKING transition. Ownership is enforced by consuming self on send/recv, and whichever endpoint is dropped last frees the heap allocation; waker.rs abstracts over std::thread::Thread and task::Waker so the same Channel<T> serves both blocking and async call paths without duplicating the state machine.

Tech Stack Pure Rust, no_std-capable, with zero mandatory runtime dependencies — Cargo.toml declares no [dependencies], only an optional loom crate gated behind internal correctness testing and dev-dependencies (tokio, async-std, criterion) for tests and benchmarks. The std and async features are both off by default. CI uses cargo-hack to build and test the full feature powerset across Linux/macOS/Windows and stable/beta/nightly/MSRV 1.85.0, plus dedicated loom and Miri jobs and separate linting/formatting/commit-style workflows. The crate targets Rust 2024 edition with an enforced MSRV.

Code Quality Testing is unusually rigorous for a small crate: dedicated suites cover sync receiving, async receiving/futures, raw-pointer FFI usage, memory-leak assertions, Miri, and a loom-based exhaustive concurrency model, backed by ten runnable examples covering send/receive ordering permutations. Errors are fully typed rather than panicking or swallowed, and every unsafe block carries an explicit SAFETY: comment justifying its invariant, with Clippy lints tightened in Cargo.toml (undocumented_unsafe_blocks = "warn", wildcard_dependencies = "deny") and CI running with RUSTFLAGS="--deny warnings".

API Design The public surface is deliberately minimal — one channel() constructor plus Sender<T>/Receiver<T> — and leans on the type system for correctness: send takes self by value so a second send is a compile error, and Receiver implements IntoFuture so .await works with no boilerplate while recv/recv_timeout/try_recv cover the synchronous cases. Naming mirrors std::sync::mpsc conventions, and every public item’s doc comments include usage examples and explicit ordering guarantees. The main friction point is that std and async are both off by default, so a first-time user must discover and enable a feature flag before recv() or .await will compile — a deliberate minimalism tradeoff the README calls out directly.

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