A channel coordinates tasks by exchanging typed messages. Use messages instead of trying to share arbitrary writable variables. The channel owns its queue and wait registrations; sender and receiver capabilities authorize specific operations without exposing that internal storage.
Send a sequence and close it
DIM jobs AS CHANNEL OF INTEGER = Channel(2)
producer = SPAWN Produce(Sender(jobs))
DO
IF LET value = Receive(Receiver(jobs)) THEN
PRINT value
ELSE
EXIT DO
END IF
LOOP
Join(producer)
SUB Produce(output AS SENDER OF INTEGER) FAILS ChannelClosed
Send(output, 10)
Send(output, 20)
Close(output)
END SUBCapacity 2 permits two buffered messages. Capacity zero is a rendezvous: Send waits until a receiver accepts the message. A full buffer parks senders; an empty open channel parks receivers. These operations wait through the scheduler rather than spinning or requiring an OS thread for each wait.
Messages have independent-value semantics. Changing a source list after sending cannot alter the delivered message. Endpoints cannot escape their channel owner’s scope or become arbitrary long-lived stored capabilities. The supported message subset uses copyable data; resource-message transfer is not a general channel API.
Close ends sending. It does not discard accepted buffered messages. Receivers get SOME(message) until drained, then NONE. A nested optional message NONE is delivered as SOME(NONE). Sending after closure reports ChannelClosed. Closing one borrowed capability is a deliberate protocol operation, not a side effect of merely ending a local parameter’s scope.
Wait for one of several events
DIM notices AS CHANNEL OF STRING = Channel(1)
Send(Sender(notices), "Ready")
Close(Sender(notices))
SELECT
CASE SOME(message) = RECEIVE(Receiver(notices))
PRINT message
CASE NONE = RECEIVE(Receiver(notices))
PRINT "Closed"
CASE AFTER Seconds(1)
PRINT "Timed out"
END SELECTScheduler SELECT commits exactly one case. Each receiver requires paired SOME and NONE cases with the same expression spelling, so the operation covers both message and closure. Current operands are named receiver parameters or Receiver(namedOwner). Case message bindings are read-only and local to that case. SELECT SEND and scheduler CASE ELSE are not implemented.
The runtime unregisters losing waits without consuming their messages. Priority rotates across repeated entries to a SELECT site when multiple cases are ready. A permanently closed receiver remains ready; stop selecting it when it is no longer useful. Fairness cannot make progress if your program stops entering the selection or performs work that never reaches a scheduling boundary.
Observe completion with a deadline
worker = SPAWN Work()
SELECT
CASE COMPLETED(worker)
PRINT "Ready to join"
CASE AFTER Seconds(1)
Cancel(worker)
END SELECT
IF Join(worker) THEN PRINT "Joined"
SUB Work()
PRINT "Working"
END SUBCOMPLETED means the task is ready to observe, not that it succeeded. Always Join to handle retained success, failure or cancellation. A timeout winning the SELECT does not erase a child error.
AFTER takes a typed Duration such as Milliseconds(100). Each entry starts a fresh relative timeout; re-entering it is not a fixed periodic cadence. Nonpositive durations are immediately ready. Unrepresentable delays or absolute deadlines raise DateTimeError before registering any waits, so functions using AFTER must catch or declare it. Operand evaluation counts against the new timeout.
Cancellation before commitment consumes no message and removes waits. After a delivery commits, cancellation may prevent the body from observing it; the committed value is still destroyed exactly once rather than put back into the channel. Design protocols around commitment and acknowledgement, rather than assuming cancellation rewinds already accepted work.