Skip to content

Select

Run one statement arm when its task or stream source becomes ready.

Updated View as Markdown

select waits across completion sources and runs the body belonging to the first ready source.

left := spawn { fetch_left() }
right := spawn { fetch_right() }
mut chosen := 0

select {
    value := left => chosen = value
    value := right => chosen = value
}

select is a statement and returns void. Arm result types and arm body types may differ.

Source expressions are evaluated once before the runtime wait. Bind complex sources to named tasks or streams first when their creation order or later cleanup matters.

Arm structure

Each arm has a binding pattern, :=, an awaitable source, =>, and a body:

pattern := source => body

The source implements the compiler’s structural selectable protocol through raw_handle() and await(). Built-in Task[T] and Stream[T] satisfy it.

For a task, selection consumes and reaps the winning task. For a stream, it receives the next value while keeping the stream usable for later selections.

The binding pattern is checked against the selected source’s produced type. Arm bodies use ordinary statement control flow and can return, break, raise an error, or update enclosing mutable state where those operations are valid.

Do not place the same single-consumer task in more than one arm. Each arm would refer to the same result and reaping obligation.

Default arms

A default arm makes selection non-blocking:

select {
    value := task => consume(value)
    default => do_other_work()
}

The runtime scans once. It runs default if no source is ready rather than registering waiters.

default is useful for a poll-and-work loop, but a tight loop can starve other work if it never reaches another safepoint:

select {
    message := inbox => handle(message)
    default => perform_bounded_work()
}

Keep the default body bounded or call something that allows cooperative progress.

Losing sources

Selection leaves non-winning tasks running and owned by the caller. It removes the temporary wait registrations but does not cancel those tasks.

This differs from Race, which creates its own arm tasks and cancels losers.

After selection, explicitly account for every losing task:

left := spawn { fetch_left() }
right := spawn { fetch_right() }
mut left_won := false

select {
    value := left => {
        left_won = true
        use(value)
    }
    value := right => use(value)
}

if left_won {
    right.cancel()
} else {
    left.cancel()
}

The winner must not be awaited or canceled again.

Streams

Streams make long-lived multiplexing possible:

mut running := true
for running {
    select {
        event := events => consume(event)
        signal := shutdown => running = false
    }
}

A closed empty stream wakes selection. The built-in selectable adapter materializes a value for that wake, so application protocols should carry an explicit end marker or separately test is_closed() instead of treating every selected stream value as evidence of a producer item.

Repeated stream selection consumes at most one buffered item per winning arm. The stream itself remains available for the next loop iteration.

Failures

If awaiting a winning source is fallible, an unhandled failure propagates through the surrounding function’s error channel. A postfix catch on the arm body can recover or map it using ordinary catch semantics.

Fairness

Selection is scheduling-dependent. If several sources are already ready, the runtime’s scan order may select an earlier arm. Do not write correctness logic that assumes statistically fair winners.

Empty selections

select {} is accepted as a void no-op. It does not suspend and introduces no effects. Prefer omitting it; an empty selection usually means dynamic arm construction belongs in a higher-level abstraction.

Effects

The runtime behavior is “wait and consume one winner,” with no loser cancellation. The current semantic checker conservatively records Spawn, Suspend, and Cancel for non-empty selections, including selections with a non-blocking default arm. Treat that row as an analysis over-approximation, not runtime behavior.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close