Single-threaded (!Send), no_std, no-atomics async channels for thread-per-core runtimes.
Overview
lochan is a family of single-threaded async channels for thread-per-core
runtimes — compio, monoio, glommio, embassy, a tokio LocalSet, and the like.
The handles hold Rc rather than Arc, so they are !Send (producer and
consumer always live on one thread) and the implementation uses no atomics —
making them strictly lighter than their Send counterparts (tokio, flume,
futures) on a single-threaded executor. no_std + alloc.
mpsc— multi-producer, single-consumer.bounded(a fixedMaybeUninitring) andunbounded(a segmented block-list that never reallocates). Non-blockingtry_send/try_recv, plus awaitablesend/recv.mpmc— multi-producer, multi-consumer. The samebounded/unboundedflavors, but bothSenderandReceiverareClone: every clone is another producer or consumer, and a delivered item goes to exactly one awaiting consumer. TheReceiveris also aStream.oneshot— a single value sent once; theReceiveris itself aFuture.
Every awaitable method returns a named Unpin + FusedFuture type, so it
drops into select_biased! without .fuse() and can be stored in a hand-rolled
driver state machine.
Installation
[]
= "0.1"
For no_std, disable default features: lochan = { version = "0.1", default-features = false }.
Example
// mpsc — sync surface
let = ;
tx.try_send.unwrap;
assert_eq!;
// mpsc — async surface
tx.send.await.unwrap;
assert_eq!;
// mpmc — Sender AND Receiver are Clone
let = ;
let rx2 = rx.clone; // a second consumer
tx.try_send.unwrap;
assert_eq!;
// oneshot — the Receiver is the future
let = ;
tx.send.unwrap;
assert_eq!;
Benchmarks
A throughput comparison against the other single-threaded (!Send) channel
crates, local-sync and local-channel, measured with cargo bench
(criterion). Each buffer + drain row queues 1024 u32 values then drains them on
a single task; oneshot times one create + send + receive. local-channel
provides only an unbounded mpsc. There is no other !Send multi-consumer
channel, so mpmc is measured against the same single-consumer peers (one
consumer).
| benchmark (1024 elements) | lochan |
local-sync |
local-channel |
|---|---|---|---|
mpsc unbounded — buffer + drain |
6.4 µs · 161 Melem/s | 5.9 µs · 174 Melem/s | 6.4 µs · 160 Melem/s |
mpmc unbounded — buffer + drain |
7.6 µs · 134 Melem/s | 5.9 µs · 174 Melem/s | 6.4 µs · 160 Melem/s |
mpsc bounded — buffer + drain |
5.8 µs · 175 Melem/s | 12.6 µs · 81 Melem/s | — |
mpmc bounded — buffer + drain |
7.5 µs · 136 Melem/s | 12.6 µs · 81 Melem/s | — |
oneshot — create + send + recv |
18.8 ns | 20.6 ns | — |
Indicative only — laptop run-to-run variance is ±10–15%, so compare within one
cargo bench run rather than against the absolute figures. On that basis,
lochan's mpsc is on par with local-sync on the unbounded channel and on
oneshot, and ~2× faster on the bounded channel (its fixed MaybeUninit ring
beats local-sync's semaphore-gated bounded queue). mpmc adds ~20–30% over
mpsc for its multi-consumer machinery — the receiver-waker list and the
panic-safe redelivery slot — yet still runs ~1.7× faster than local-sync on
the bounded path.
License
lochan is under the terms of both the MIT license and the Apache License
(Version 2.0).
See LICENSE-APACHE, LICENSE-MIT for details.
Copyright (c) 2026 Al Liu.