Skip to content

Channel

Channel<T> is the primary synchronization primitive for concurrent tasks in Chuks. Channels are typed — the element type is fixed at construction and enforced by the type checker on every send and receive.

import { Channel } from "std/channel";

Channel<T> is a generic class. The type parameter <T> is required — every channel carries values of a single concrete type.

// Buffered channel of ints — can hold up to 5 values before send blocks
const numbers: Channel<int> = new Channel<int>(5);
// Unbuffered channel of strings — send blocks until a receiver is ready
const messages: Channel<string> = new Channel<string>(0);

Sends a value into the channel. Blocks if the buffer is full (or the channel is unbuffered and no receiver is ready).

numbers.send(42);
// numbers.send("hi"); // ❌ type error — Channel<int>

Receives a value from the channel. Blocks until a value is available. Returns null once the channel is closed and drained.

var v: int? = numbers.receive();
if (v != null) {
println("got: " + string(v));
}

The result is T? (nullable) so you can detect channel closure with a simple null check.

Closes the channel. No more values can be sent. Receivers can still drain any buffered values; once the channel is empty, receive() returns null.

numbers.close();

Attempts to receive without blocking. Returns a map with two fields:

  • ok: true — a value was available; value is the received T?.
  • ok: false — the channel was empty (a blocking receive would have waited).
const result = numbers.tryReceive();
if (result["ok"]) {
println("got: " + string(result["value"]));
} else {
println("nothing yet");
}

Attempts to send without blocking. Returns true if the value was queued, false if the buffer is full.

const sent: bool = numbers.trySend(99);
if (!sent) {
println("buffer full, dropping");
}

The idiomatic pattern for draining a channel is a for loop driven by receive():

for (
var v: int? = numbers.receive();
v != null;
v = numbers.receive()
) {
println(v);
}

This loop exits cleanly once the producer calls numbers.close() and the buffer is drained.

import { Channel } from "std/channel";
function main() {
const ch: Channel<int> = new Channel<int>(0); // unbuffered
spawn produce(ch);
for (
var v: int? = ch.receive();
v != null;
v = ch.receive()
) {
println(v);
}
println("done");
}
function produce(ch: Channel<int>): void {
for (var i = 0; i < 5; i++) {
ch.send(i);
}
ch.close();
}

Output:

0
1
2
3
4
done

For a full worker-pool walkthrough — fanning jobs out to multiple workers and collecting results — see the Worker Pool with Channels tutorial.

MemberTypeDescription
new Channel<T>(bufferSize)constructorCreate a channel of T with the given buffer size.
send(value: T)voidSend a value (blocks if buffer is full).
receive()T?Receive a value (blocks if empty); null on close.
close()voidClose the channel.
tryReceive(){ value: T?, ok: bool }Non-blocking receive.
trySend(value: T)boolNon-blocking send.