Area
Incan Language (syntax/semantics), Compiler (frontend/backend), Stdlib (std.async)
Summary
Introduce race as an import-activated std.async vocabulary form for "first-completion wins" concurrency, together with an Incan-native Awaitable[T] protocol that formalizes what await means in generic code. The architecture is deliberately layered: await remains a core language feature, Awaitable[T] is the Incan-facing protocol behind it, and race is import-activated vocabulary that desugars to std.async helper calls.
RFC document: 039_race_for_awaitable_concurrency.md
Motivation
The stdlib currently lacks a clean way to express generic awaitables. A timeout helper should be straightforward Incan — pub async def timeout_option[T, F with Awaitable[T]](seconds: float, task: F) -> Option[T] — but today that contract cannot be expressed and preserved cleanly through the frontend and lowering pipeline. This RFC closes that gap.
Proposal sketch
# Basic race — first awaitable to complete wins, loser is cancelled
result = race for value:
await fast() => value
await slow() => value
# Union result (RFC 029) — branches may produce different types
result: str | int = race for value:
await fetch_text() => value
await fetch_count() => value
# Timeout becomes ordinary std.async composition
pub async def timeout_option[T, F with Awaitable[T]](seconds: float, task: F) -> Option[T]:
return race for value:
await task => Some(value)
await sleep(seconds) => None
# Direct helper use (the desugaring target)
result = await std.async.race(
std.async.arm(fast(), (value) => value),
std.async.arm(slow(), (value) => value),
)
Semantic layering
- Core:
await and Awaitable[T] — await expr is valid only if expr satisfies Awaitable[T], result type is T
- Library:
RaceArm[R], arm(awaitable, on_win), race(*arms: RaceArm[R]) in std.async
- Vocabulary:
race for value: syntax — import-activated via import std.async, desugars to the library layer
Key design decisions
race is not select — SELECT is reserved for future query language surfaces; Go-style select is channel-oriented while this RFC is about arbitrary awaitables
race is not async match — match inspects a value that already exists; race waits on several awaitables and cancels losers
- Cancellation is cooperative; losing arms are dropped, triggering their normal cleanup
- Tie-breaking in v1: first arm in source order wins (deterministic)
Awaitable[T] follows RFC 028's language-first philosophy — specified in Incan terms, mapped to Rust futures in the backend
- The variadic helper shape
race(*arms: RaceArm[R]) depends on RFC 038; fixed-arity helpers are an acceptable bridge
Unresolved questions
- Should
Awaitable[T] be user-implementable in v1, or compiler-recognized only?
- Should
std.async.select be renamed to std.async.race with compatibility exports?
- Does RFC 027 need a dedicated expression-block surface kind for
race for value:?
- Should a later version add a more general pattern-binding
race form, or is race for value: plus ordinary match sufficient?
- Should a later version add
default arms, guard expressions, or unbiased scheduling options?
Layers affected
- Core language / Typechecker —
Awaitable[T] builtin protocol; await type-checking; bound lowering for F with Awaitable[T]
- Parser / Vocabulary —
race for value: import-activated syntax via RFC 027 vocabulary/desugaring machinery
- IR Lowering — desugar
race for value: to std.async.race(std.async.arm(...), ...) calls
- Stdlib (
std.async) — RaceArm[R], arm(...), race(...) helpers; rewrite std.async.select placeholders
- Rust backend —
Awaitable[T] → Rust future semantics; race(...) → tokio::select! or equivalent
Related RFCs
- RFC 023 (Compilable stdlib & Rust module binding)
- RFC 027 (Vocabulary/desugaring — activation mechanism for
race)
- RFC 028 (Trait-based operator overloading —
Awaitable[T] follows the same language-first pattern)
- RFC 029 (Union types — natural return type when race arms produce different types)
- RFC 035 (First-class function references — named handlers work as
arm(...) callbacks)
- RFC 038 (Variadic args — the ideal
race(*arms: RaceArm[R]) helper shape)
Checklist
Area
Incan Language (syntax/semantics), Compiler (frontend/backend), Stdlib (
std.async)Summary
Introduce
raceas an import-activatedstd.asyncvocabulary form for "first-completion wins" concurrency, together with an Incan-nativeAwaitable[T]protocol that formalizes whatawaitmeans in generic code. The architecture is deliberately layered:awaitremains a core language feature,Awaitable[T]is the Incan-facing protocol behind it, andraceis import-activated vocabulary that desugars tostd.asynchelper calls.RFC document:
039_race_for_awaitable_concurrency.mdMotivation
The stdlib currently lacks a clean way to express generic awaitables. A timeout helper should be straightforward Incan —
pub async def timeout_option[T, F with Awaitable[T]](seconds: float, task: F) -> Option[T]— but today that contract cannot be expressed and preserved cleanly through the frontend and lowering pipeline. This RFC closes that gap.Proposal sketch
Semantic layering
awaitandAwaitable[T]—await expris valid only ifexprsatisfiesAwaitable[T], result type isTRaceArm[R],arm(awaitable, on_win),race(*arms: RaceArm[R])instd.asyncrace for value:syntax — import-activated viaimport std.async, desugars to the library layerKey design decisions
raceis notselect—SELECTis reserved for future query language surfaces; Go-styleselectis channel-oriented while this RFC is about arbitrary awaitablesraceis notasync match—matchinspects a value that already exists;racewaits on several awaitables and cancels losersAwaitable[T]follows RFC 028's language-first philosophy — specified in Incan terms, mapped to Rust futures in the backendrace(*arms: RaceArm[R])depends on RFC 038; fixed-arity helpers are an acceptable bridgeUnresolved questions
Awaitable[T]be user-implementable in v1, or compiler-recognized only?std.async.selectbe renamed tostd.async.racewith compatibility exports?race for value:?raceform, or israce for value:plus ordinarymatchsufficient?defaultarms, guard expressions, or unbiased scheduling options?Layers affected
Awaitable[T]builtin protocol;awaittype-checking; bound lowering forF with Awaitable[T]race for value:import-activated syntax via RFC 027 vocabulary/desugaring machineryrace for value:tostd.async.race(std.async.arm(...), ...)callsstd.async) —RaceArm[R],arm(...),race(...)helpers; rewritestd.async.selectplaceholdersAwaitable[T]→ Rust future semantics;race(...)→tokio::select!or equivalentRelated RFCs
race)Awaitable[T]follows the same language-first pattern)arm(...)callbacks)race(*arms: RaceArm[R])helper shape)Checklist