Skip to main content

Crate lochan

Crate lochan 

Source
Expand description

lochan

Single-threaded (!Send), no_std, no-atomics async channels for thread-per-core runtimes.

github LoC Build codecov

docs.rs crates.io crates.io license

§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 fixed MaybeUninit ring) and unbounded (a segmented block-list that never reallocates). Non-blocking try_send / try_recv, plus awaitable send / recv.
  • mpmc — multi-producer, multi-consumer. The same bounded / unbounded flavors, but both Sender and Receiver are Clone: every clone is another producer or consumer, and a delivered item goes to exactly one awaiting consumer. The Receiver is also a Stream.
  • oneshot — a single value sent once; the Receiver is itself a Future.

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)lochanlocal-synclocal-channel
mpsc unbounded — buffer + drain6.4 µs · 161 Melem/s5.9 µs · 174 Melem/s6.4 µs · 160 Melem/s
mpmc unbounded — buffer + drain7.6 µs · 134 Melem/s5.9 µs · 174 Melem/s6.4 µs · 160 Melem/s
mpsc bounded — buffer + drain5.8 µs · 175 Melem/s12.6 µs · 81 Melem/s
mpmc bounded — buffer + drain7.5 µs · 136 Melem/s12.6 µs · 81 Melem/s
oneshot — create + send + recv18.8 ns20.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.

Modules§

mpmc
Multi-producer, multi-consumer channel.
mpsc
Multi-producer, single-consumer channel.
oneshot
One-shot channel: a single value sent once from the producer to the consumer.