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.
What can initialize an async let
Section titled “What can initialize an async let”| Initializer | Launch behavior |
|---|---|
| E# async function | Already returns an in-flight awaitable; E# records it. |
BCL awaitable such as Task.FromResult(x) | Used directly. |
| Synchronous E# function call | Wrapped in Task.Run so it really overlaps. |
| Non-call expression | Rejected; 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.
Result values
Section titled “Result values”? 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)}