Skip to content

Streams

Send values through a shared bounded asynchronous channel.

Updated View as Markdown

Stream[T] is a shared handle to a bounded FIFO channel. Copies of the handle refer to the same control block, which makes it suitable for communication between spawned producers and consumers.

events: Stream[int] = Stream.new[int](16)

The capacity defaults to 16. A non-positive capacity is normalized to 1. Capacity is fixed for the lifetime of the stream. It bounds live buffered elements, not the total number that can pass through over time.

events := Stream.new[Event](64)
producer := spawn {
    for event in load_events() {
        if !events.send_await(event) {
            return
        }
    }
    events.close()
}

Sending

send(value) is non-blocking:

accepted: bool = events.send(42)

It returns false when the stream is full or closed. The value is enqueued and a waiting consumer is woken when it returns true.

send_await(value) applies backpressure:

accepted := events.send_await(42)

It suspends while the channel is full, then returns true once the value is stored. It returns false if the stream is or becomes closed.

Method Full stream Closed stream
send(value) Return false immediately Return false
send_await(value) Suspend until space or close Return false

Use send when dropping, coalescing, or retrying is an explicit producer policy. Use send_await when every accepted value must be delivered and the producer should slow down with the consumer.

Receiving

recv() is a non-blocking buffer operation:

match events.recv() {
    Some(event) => consume(event)
    None => {}
}

It removes the oldest buffered item. None means that no item is currently buffered; by itself it does not distinguish an open empty stream from a closed drained stream. Check is_closed() when that distinction matters.

To wait for incoming data, use the stream as a select source. Selection blocks until the stream becomes readable or closes.

select {
    event := events => consume(event)
    _ := shutdown => stop()
}

recv() frees one ring slot and wakes a producer parked in send_await. Received values move out of the channel; the dead slot is not read again.

Closing

close() rejects future sends and wakes waiting producers and consumers. Buffered elements remain available:

events.close()

for Some(value) := events.recv() {
    consume(value)
}

Closing more than once leaves the stream closed. Producers should treat send_await(...) == false as a request to stop producing.

Close does not discard buffered values. Consumers can drain them with repeated recv() calls and finish when both is_closed() and recv() == None. Because state can change between calls, keep the decision inside one cooperative consumer loop rather than splitting it across tasks.

State

The current inspection methods are:

Method Meaning
len() Number of buffered, unconsumed items
is_empty() Whether len() is zero
is_closed() Whether close() has been called

These are snapshots in concurrent code. Do not check is_empty() and then assume a later recv() will observe the same state.

Selection boundary

A closed empty stream wakes a blocked select. The current selectable adapter must materialize a T for every winning arm; on that close-only wake it uses a zeroed value. Null-like scalar and handle representations are drop-safe, but a value enum whose zero-tag variant owns payload data is not yet a sound selected element type for this path.

Use a separate shutdown task, a scalar sentinel whose zero value is reserved for closure, or check stream closure outside selection when clean end-of-stream must be distinguished from a real element.

Lifetime

Drain values before abandoning a stream when their destruction matters. The shared handle deliberately has no ordinary per-copy destructor, because several copied handles alias the same channel control block.

That design means abandoning a stream with buffered managed values can retain their allocations until the enclosing runtime unit is reclaimed. Close stops new sends, but draining transfers and eventually drops each buffered value.

The stream handle is for in-process structured tasks. Its raw selectable handle is runtime state, not a serializable channel identifier.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close