This library is in flux. APIs may change without notice. Generated from source with koruc 0.1.7 on 10/5/2026.
Channel
~import std/channelKoru Standard Library: Channels — bounded MPMC channels over proto elements.
channel.kz · 6 tors
Koru Standard Library: Channels — bounded MPMC channels over proto elements. · 63 more lines
Koru Standard Library: Channels — bounded MPMC channels over proto elements.
std/channel:new(inbox, capacity: 256) { reading: Reading } // declare
std/channel(inbox) // program-wide consumer join
! reading r |> handle(r) // competing consumer arm
! closed |> shutdown() // close arm (reserved word)
std/channel:send(inbox, r) | ok |> .. | full |> .. | closed |> ..
std/channel:send(inbox, reading: r) | .. // kind-labeled form (multi-kind)
std/channel:recv(inbox) | some v |> .. | none |> .. | closed |> ..
std/channel:close(inbox) | ok |> ..
THE SHAPE (docs/CHANNEL.md, ruled 2026-09-24): the `{ }` body is a BRANCH
TABLE — each `name: Proto` entry is one message kind: the arm word plus
the proto its payload carries. Multi-kind is legal
(`{ reading: Reading, alert: Alert }` → `! reading` + `! alert`); each
kind gets its own ring — one firing carries one kind, and a value of a
kind never queues behind another kind. `capacity:` is required, a power
of two above zero; `capacity: 0` is the rendezvous spelling — deferred,
refused with teaching.
`std/channel(name)` is the bare-reference JOIN (the `default` tor, a [pre]
rewrite to `consume` — the store `default`→`watch` pattern): `!` arms on
it attach program-wide and are COLLECTED BY `new`, which splices them into
the generated drain — consumers are statically enumerable, one value goes
to one unit, join order. Arms under `channel:new` itself refuse.
What the declaration emits into the channel's home module:
- `pub const <Proto> = struct { ... }` per kind's proto — skipped when
another consumer (std/list:new) already derived it.
- `__koru_chan_<n>_<kind>_ring: MpmcRing(<Proto>, cap)` per kind, plus
one `__koru_chan_<n>_closed` atomic — rings.kz carries no close bit;
closed is channel semantics, not queue mechanics.
- `__channel_send_<n>_<kind>` { value: Proto } | ok | full | closed —
one send tor per kind.
- `__channel_recv_<n>` {} | some Proto | none | closed — single-kind
channels only (recv can't spell one payload type on a multi-kind
channel; it refuses with teaching at the site).
- `__channel_close_<n>` {} | ok — sets the bit, fires every `! closed`
unit in join order.
- `__channel_<n>_<kind>_<i>` / `__channel_<n>_closed_<i>` — one generated
unit per `!` arm, the emitVerbUnit convention.
- `<n>-step` / `<n>-live` / `<n>-wait` — the pump participant verbs, the
same names a `! step` store exposes, so a channel joins a pump by name:
std/pump(main)
! step |> inbox-step()
! live |> inbox-live()
! wait i |> inbox-wait(i: i)
step: one drain pass — for each kind, each of that kind's consumer units
attempts one dequeue in join order (competing: first pop wins the value);
returns items moved. live: 0 once closed AND every kind's ring is drained.
wait: `-> i128`, 1ms — a same-thread re-poll interest (a scalar IS a
deadline); the cross-thread eventfd rung is deliberately unbuilt.
`closed`-arm timing: the units fire inside `std/channel:close` — the
transition point — so a channel never joined to a pump still runs them.
Ordering law (same as pump/store): the declaration precedes its uses in
document order — transforms run in source order and the chain steps find
the channel by its emitted marker.
// CHANNEL.NEW — `std/channel:new(<name>, capacity: N) { kind: Proto, ... }`:
// declare the channel, then collect every `consume` join in the program and
// splice its `!` arms into the generated drain.
~[keyword|comptime|transform] pub tor new {
expr: Expression,
source: ?Source,
invocation: *const Invocation,
item: std/compiler:*const Item,
program: std/compiler:*const Program,
reporter: std/compiler:*ErrorReporter,
allocator: std.mem.Allocator
} -> SiteResult// CHANNEL.DEFAULT — the bare-reference join `std/channel(<name>) ! arms`.
// A [pre]-phase rewrite to the internal `consume` verb — the store
// `default`→`watch` pattern — so `new` collects every join program-wide
// before the main phase runs.
~[keyword|comptime|transform|pre] pub tor default {
expr: Expression,
invocation: *const Invocation,
item: std/compiler:*const Item,
program: std/compiler:*const Program,
reporter: std/compiler:*ErrorReporter,
allocator: std.mem.Allocator
} -> SiteResult// CHANNEL.CONSUME — the join site's own transform (internal verb; also the
// literal spelling). By the time this fires on a well-formed program, `new`
// has already collected the arms — the site self-erases. Firing with the
// decl still untransformed means the join precedes it (rung-one ordering);
// no decl anywhere is a plain unknown-channel diagnostic.
~[keyword|comptime|transform] pub tor consume {
expr: Expression,
invocation: *const Invocation,
item: std/compiler:*const Item,
program: std/compiler:*const Program,
reporter: std/compiler:*ErrorReporter,
allocator: std.mem.Allocator
} -> SiteResult~[keyword|comptime|transform] pub tor send {
expr: Expression,
invocation: *const Invocation,
item: std/compiler:*const Item,
program: std/compiler:*const Program,
reporter: std/compiler:*ErrorReporter,
allocator: std.mem.Allocator
} -> SiteResult~[keyword|comptime|transform] pub tor recv {
expr: Expression,
invocation: *const Invocation,
item: std/compiler:*const Item,
program: std/compiler:*const Program,
reporter: std/compiler:*ErrorReporter,
allocator: std.mem.Allocator
} -> SiteResult~[keyword|comptime|transform] pub tor close {
expr: Expression,
invocation: *const Invocation,
item: std/compiler:*const Item,
program: std/compiler:*const Program,
reporter: std/compiler:*ErrorReporter,
allocator: std.mem.Allocator
} -> SiteResult