ADR-149: A Channel Is std’s, and Only Bounded

Status: Accepted Date: September 19, 2026 Related: ADR-141 D2 (a construct a page writes before its record), ADR-291 (a let takes a flat tuple of names), ADR-123 (crosses, and what may go to another thread), ADR-055 (a task is a coroutine), ADR-278 (T?), Part II 12.5

1. Context

Part II 12.5 writes let (tx, rx) = channel::bounded(100), tx.send(…) and rx.recv(). Nothing in it needs syntax: two values, two methods, and the tuple let (ADR-291). The questions are where it lives and what it promises.

2. Decisions

D1 — std::channel, with bounded(n) and nothing else.

use std::channel

let (tx, rx) = channel::bounded(100)
spawn fn { tx.send("done") }
let msg = rx.recv() ?? "nobody sent anything"

A module in std over the runtime’s queue, with four entries; bounded hands back the tuple a let destructures. No language construct: two methods say everything the page’s example says. A capacity below one is refused with its reason rather than turned into something else, because bounded(0) is the rendezvous channel §4 leaves undecided.

D2 — send pauses when the channel is full, and the ledger says so.

send has no sync in its entry, so a sync body cannot send on a channel and the compiler says which promise is in the way (NK2202, asked by the type checker for a method call as for a free one) — without a rule about channels, because the column is the rule. That is the point of a bounded channel: back-pressure is a pause, and a pause is a thing this language’s ledger already talks about.

D3 — recv hands back a T?, and null means every sender is gone.

Not a failure: a closed channel is the ordinary end of a stream, and ?? is what reads it (Part I 3.5). A for over a receiver is the same loop one level up and waits for ADR-105’s Seq to have an entry for it.

D4 — The value type must cross, checked where the sender moves.

A channel carries values to another thread, so the type must answer ADR-123’s crosses. It is checked where tx moves into a spawn, as any move is — not at send, and not by a rule of its own: the crossing analysis already asks that question at that place, with Sender and Receiver among the containers it looks through.

D5 — There is no unbounded().

A capacity is a promise about memory, and this language makes programs say their promises. An unbounded channel is a memory leak with a name and a send that never pauses, which is the one thing a program cannot reason about under load. A program that wants unbounded writes a large number — and has said it.

3. Consequences

4. What this does not decide

This site is open source. Improve this page.