Expand description
lochan
§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
[dependencies]
lochan = "0.1"For no_std, disable default features: lochan = { version = "0.1", default-features = false }.
§Example
// mpsc — sync surface
let (tx, mut rx) = lochan::mpsc::bounded::<u32>(16);
tx.try_send(1).unwrap();
assert_eq!(rx.try_recv(), Ok(1));
// mpsc — async surface
tx.send(2).await.unwrap();
assert_eq!(rx.recv().await, Some(2));
// mpmc — Sender AND Receiver are Clone
let (tx, rx) = lochan::mpmc::unbounded::<u32>();
let rx2 = rx.clone(); // a second consumer
tx.try_send(3).unwrap();
assert_eq!(rx2.try_recv(), Ok(3));
// oneshot — the Receiver is the future
let (tx, rx) = lochan::oneshot::channel::<u32>();
tx.send(42).unwrap();
assert_eq!(rx.await, Ok(42));§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.