vilan 0.9.0
Higher-order functions adapt to async callbacks. map is one function, not two: passing an async closure instantiates an async copy of the receiving function — its calls through the parameter are awaited, sequentially (each callback settles before the next begins) — while every sync call site keeps the untouched original. Adaptation follows the closure through plain parameters transitively (helper(xs, f) forwarding into map adapts end-to-end), an adapting function traverses a snapshot of its receiver so interleaved work can't tear the iteration, and it stops honestly at the boundaries: a parameter marked sync declares the synchronous contract (the reactive graph's recompute positions — Signal::map, turn, batch, the UI render callbacks — are sync), host (external) functions can't await your closure (unless a parameter is declared async |…| T — the typed channel), and trait/generic dispatch has no static callee to instantiate. When the elements are independent, opt into concurrency with the spawn-then-settle idiom: .map(|x| async work(x)) then Task::settle_all(tasks).
Spawning grew a spine: Task<T>, and nurseries to own them. async expr now yields a Task<T> — an eager, opaque handle; copying it refers to the same task. Every task absorbs its own failure at construction: a spawned panic can never crash the program as a host "unhandled rejection" — a later await receives it, and a task nobody observes reports the error to the console stamped with the function that spawned it, then execution continues. Task::settle_all joins many; Task::race yields the first to settle. Raw host promises stay Promise<T> at the extern seam, and await unwraps both.
nursery(body) is structured concurrency (std::task): every task spawned in the body's dynamic extent — by the body, by anything it calls, by the tasks themselves — is joined before the nursery returns the body's value. Failures follow the first-observed rule: a body throw wins, otherwise the earliest-settled task failure, re-raised from the nursery call with its spawn origin while every other task is absorbed. n.cancel() aborts the whole extent — the nursery's AbortSignal rides ambiently into sleep and fetch, so cancellation cuts in-flight IO short instead of waiting it out (a live e2e cancels a fetch against a hanging endpoint and joins in ~3s instead of 60), cancellation rejections are absorbed echoes rather than errors, nurseries chain so an outer cancel reaches nested extents, and Task::race + n.cancel() is the race idiom. The first real failure cancels the same way, so one task's error stops its siblings' work at settle time — not when the join happens to look. Spec §7.7 is the contract; the async tour walks it.
Asyncness now rides every value channel. async |T| U is accepted on struct fields and function return types (calls through a field read or a returned closure await implicitly), unannotated bindings adopt asyncness from any value they hold — including mut rebinds — and storing an async closure where a plain value-returning closure type is declared (a field, a return type) is a compile error instead of a promise wearing the wrong type. Void-returning positions keep spawn semantics, which is why UI handlers await freely with no ceremony. The standard library's own transport and draft plumbing was migrated off its workarounds in the process.
Variadic tuple bounds are enforced. T: (2..) and (..: Display) parsed since variadics landed but checked nothing; arity ranges and per-element trait bounds now hold at every call and construction site, with the note pointing at where the bound was declared.
Editor and diagnostics tail. Notes can point into another file (the "declared here" half of a cross-module error lands in the right source); inlay type hints for inferred let bindings; semantic tokens gained modifiers; parse errors name the split (a! == b vs a != b) instead of dumping the expected-token soup; x.field() on a closure-valued field steers to (x.field)(); and multi-file diagnostic publishing dedupes across dependents, so fixing a shared module clears its dependents' stale squiggles in one pass.