Skip to content

Concurrent bindings with async let

async let is for fan-out. Its initializer starts at the declaration; the binding is awaited when its value is first referenced. This is different from writing two ordinary await expressions, which run serially.

func load(n: int) -> int = await Task.FromResult(n * 10)
func total() -> int {
async let left = load(2) // starts now
async let right = load(3) // starts now too
let r = right // first join is right
let l = left // then join left
return l + r
}

The first-use rule is observable: right is joined before left above, regardless of declaration order. Keep the bindings close to the work they start, and consume each only on paths that actually need it.

InitializerLaunch behavior
E# async functionAlready returns an in-flight awaitable; E# records it.
BCL awaitable such as Task.FromResult(x)Used directly.
Synchronous E# function callWrapped in Task.Run so it really overlaps.
Non-call expressionRejected; wrap the intended work in Task.Run yourself.
func compute(n: int) -> int = n * 2
func combine() -> int {
async let a = compute(4) // runs on the pool
async let b = compute(3)
return a + b
}

async let is the right tool when you know you need several results. A plain synchronous force-on-use binding is the lighter choice for one uncolored async operation that merely overlaps nearby synchronous work.

? applies after the implicit join, so a failing Result exits at the first consumption point:

func load(n: int) -> Result<int, string> {
let value = await Task.FromResult(n)
if value < 0 { return error("negative") }
return ok(value)
}
func combine() -> Result<int, string> {
async let a = load(2)
async let b = load(-1)
let av = a?
let bv = b? // joins b, then returns error("negative")
return ok(av + bv)
}