Async and structured concurrency
An async function describes work that may suspend. Its source result remains
T or T throws E; calling it produces task-shaped work that must be awaited
or started as a structured child.
async function fetchValue(id: i32): i32 { return id;}
async function main(): i32 { const value = await fetchValue(42); return value;}await starts and joins the operation at that point. The current task may
suspend; the OS thread or executor is free to perform other work.
Start work with task
Section titled “Start work with task”task starts a hot structured child immediately:
async function main(): i32 { const firstTask = task fetchValue(20); const secondTask = task fetchValue(22);
doSynchronousWork();
const first = await firstTask; const second = await secondTask; return first + second;}The benefit is the interval between creation and join: both child operations
can make progress while the parent does other work. await task operation() is
usually equivalent to directly awaiting the operation and creates no useful
overlap.
The task keyword creates a Task<T, E> handle. It does not create an OS
thread. Use thread.spawn when the work must run on a new native thread.
Lexical scopes join children
Section titled “Lexical scopes join children”Every child belongs to a scope. An async function body is an implicit root task
scope; async scope creates a narrower join boundary:
async function refresh(): void throws NetworkError { async scope { task refreshUser(); task refreshPosts(); }
publishRefreshedUI(); // both children have finished}Leaving the scope joins every child. This remains true when a handle is not awaited explicitly, so structured work cannot silently become a detached background operation.
The scope is useful because two sequential await expressions would not run
the operations concurrently:
await refreshUser();await refreshPosts(); // starts after refreshUser completesErrors stay typed
Section titled “Errors stay typed”Combine suspension with the ordinary error vocabulary:
const value = try await fetchValue(42);const result = attempt await fetchValue(42);try await joins and propagates E. attempt await joins and produces
Result<T, E> for local matching.
When a child failure propagates out of a scope, live siblings are cancelled and joined before the parent completes. Cancellation is a structural task outcome, not an invented variant of every domain error type.
Caller cancellation
Section titled “Caller cancellation”A task handle owns the authority to join or cancel one child:
async scope { const pending = task download();
if (noLongerNeeded()) { await pending.cancel(); } else { const result = await pending; publish(result); }}cancel() requests cancellation and joins. It does not abandon the task frame
or its owned values. Cleanup runs exactly once whether completion or
cancellation wins.
Delays and cooperative yielding
Section titled “Delays and cooperative yielding”An async delay suspends without blocking the executor thread:
import { delay } from "std/time";
async function finishLater(): void { await delay(1000);}Synchronous sleep is a different API: it blocks the current OS thread and is
appropriate only when blocking is intentional.
Long CPU loops on an async executor can cooperate explicitly:
import { scheduler } from "std/async";
async function index(in records: Array<Record>): void { for (const record of records) { indexRecord(record); await scheduler.yield(); }}scheduler.yield() requeues the current task and creates a cancellation
observation point. It does not sleep, switch executors, or yield a generator
element.
Dynamic task scopes
Section titled “Dynamic task scopes”async scope updates {} binds a TaskScope handle when callbacks must submit
work dynamically. scope.schedule(thread.main, operation) returns a
TaskControl, which can request cancellation but does not own joining. The
scope remains the sole lifetime owner and rejects new submissions while it
closes.
This advanced form is especially useful for native callbacks. Ordinary lexical
child work should prefer unnamed async scope and the task keyword.
Runnable examples: async overview, scopes, bounded channels, and cancellation.