Status
Accepted
Context
ADR-025 establishes select as an expression composed of cases, each producing a case value that is passed into an explicit body via the /> operator.
As usage has evolved, several related ergonomics issues have emerged:
- Many
selectexpressions only need the selected case value, making the body redundant. - Task timeouts are currently expressed using a special
await <task> within <duration>form, even thoughselectalready models coordination. - Call-chain style flows (
recv … /> match { … }) often need access to both a destructured value and the original value, forcing unnecessary reconstruction.
All three issues stem from the same root cause: existing semantics are expressive, but not directly exposed in a composable, ergonomic way.
Decision
Optional select case bodies
A select case may omit the /> body.
When omitted, the select expression evaluates to the case value of the selected case, as defined in ADR-025.
A body-less case is semantically equivalent to a body that returns its input unchanged.
select {
recv c
_
}
If the _ case is selected, the result of the select expression is nil.
Readiness, fairness, and cancellation semantics are unchanged.
Await as a select case
select gains a new case header form:
await <taskExpr>
The case becomes ready when the task completes and any unselected tasks are cancelled.
The case value is the result of awaiting the task. If the task fails, selecting this case throws the same runtime error as the current await <taskExpr>.
This allows task coordination and timeouts to be expressed uniformly using select.
select {
await h1
await h2
after 500 /> fn(t) { throw Error{type:"Timeout"} }
}
This pattern enables racing tasks and waiting for the first to complete.
CLARIFICATION: after configured with 0 is ignored and never selected, use _ instead.
The special form:
await <task> within <duration>
is replaced by this composition and the both await and within forms are removed.
Whole-value binding in match arms
match arms may bind the entire matched value while also applying a pattern using the form:
<ident> @ <pattern> => <expr>
If the pattern matches, <ident> is bound to the original scrutinee value for that arm.
This is especially useful in call-chain expressions where destructuring is needed for control flow, but the original value should be returned or forwarded unchanged.
recv c1 /> match {
box @ Full{value: 100} => box
_ => :done
}
Whole-value bindings are scoped to the match arm and do not affect match ordering or exhaustiveness.
Consequences
Positive
- Reduces syntactic noise in common
selectpatterns - Makes ADR-025 case values directly observable and usable
- Eliminates special-case
awaittimeout syntax - Enables clean task racing via
select - Improves call-chain ergonomics without introducing new primitives
- Fully backward-compatible with existing semantics
Negative
- A failed awaited task may be selected fairly alongside other ready cases, resulting in immediate error propagation
Neutral
- No changes to fairness, readiness, or cancellation rules
- Preference and ordering must continue to be expressed structurally