ADR-187: Rust Has A Stable async Closure, And A Premise Under Twelve Sentences Is False

Status: Superseded by ADR-277 — its D1 is D13 there and D2 is D16 (its third point displaced by ADR-233); D3 (answered by ADR-192 D1) and D4 are not carried Date: September 22, 2026 Target Version: Nikaia 0.0.138 Corrects the premise of: ADR-055 §6 step 3, ADR-033 D10’s coda, ADR-122 §1 and D1 — none of which is rewritten, because a record says what was decided when Related: ADR-009 D4 (measure before choosing a shape), ADR-102 D2 (the type says what the code may do)

1. Context

Twelve live sentences in this tree — a std module header, five emitter comments, three test doc comments, the roadmap, spec-promises.md and #187 — say some form of:

Rust has no stable async closure.

It is false, and it was false when each of them was written. Measured on this repository’s own toolchain, rustc 1.94.1, edition 2021, stable, with no feature gate and no RUSTC_BOOTSTRAP:

async fn twice<F: AsyncFn(i32) -> i32>(f: F) -> i32 { f(1).await + f(2).await }
let f = async |x: i32| -> i32 { x * 10 };

compiles and runs, as do AsyncFnMut, AsyncFnOnce and async move |…|.

A false premise is worse than a wrong decision, because a decision is argued against and a premise is inherited. Each of the twelve sentences is someone’s reason for a shape, and every reader who takes the shape on that reason takes the falsehood with it. That is what this record is for.

And it is not a fact that expired. The async closure has been on stable since well before the earliest of these records. Nothing here was overtaken by the language below; it was never checked against it — which is ADR-009 D4’s rule (measure before choosing a shape) not applied to the shape’s own premise.

2. Decisions

D1 — The premise is false, and the measurement is in the tree.

benches/handler gains a row. Against a plain closure as the floor, on this machine, best of five, control tying:

row ns/call ×
plain closure (impl Fn) 0.31 1.0
boxed future (impl Fn(A) -> Pin<Box<dyn Future>>) — ADR-122 D1’s shape 11.99 38.3
async closure (impl AsyncFn(A) -> R) — the shape said not to exist 1.37 4.4
plain closure, twice (the control) 0.29 —

The shape chosen because it did not exist costs 8.7× what it does.

D2 — Where the conclusion survives, the reason is replaced and the conclusion is not.

Three of the sentences justify a shape that is right for a different reason, and the correction is to say that reason:

D3 — Where the conclusion rested on the premise alone, it is reopened as a question and not rewritten here.

ADR-122 D1 — a parameter whose type may pause lowers to a closure returning a boxed future — has no surviving reason of the kind D2 supplies, and D1’s measurement is what makes that matter: 38× against a floor, where the alternative is 4.4×.

It is not this record’s to overturn, and the reason is D1’s own second half: a reader can tell what a signature costs by reading it, one shape for a run parameter and a kept one alike. impl AsyncFn is a bound and not a type, so a kept parameter — one stored in a struct — still needs a box or a type parameter of its own, and taking the cheap shape for the run case splits the two apart again. That is a trade between coherence and 8.7×, which is the owner’s, so it goes to open-decisions.md in that page’s shape.

D4 — The records keep their text.

ADR-055, ADR-033 and ADR-122 are not edited. A record says what was decided when, and a premise quietly corrected inside one is a record that no longer explains the code it produced. What is corrected is every living sentence — source comments, test doc comments, the roadmap, spec-promises.md — plus a row in the index’s superseded and narrowed table, which is where this tree already says this part of that record no longer holds.

3. Consequences

4. What this does not decide

5. What is built

At 0.0.138.

This site is open source. Improve this page.