Skip to content

Commit 694d0df

Browse files
Why no AsyncFnOnce::Output
1 parent 59e0eb2 commit 694d0df

File tree

1 file changed

+34
-0
lines changed

1 file changed

+34
-0
lines changed

text/3668-async-closure.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,40 @@ Reusing the `async` keyword allows users to understand what an `async Fn() -> T`
561561

562562
We could desugar `async FnOnce() -> T` directly to `FnOnce<(), Output: Future<Output = T>>`. It seems overly complicated for an implementation detail, since users should never care what's *behind* the `AsyncFnOnce` trait bound.
563563

564+
### Why do we recommend the `AsyncFnOnce::Output` type remains unstable, unlike `FnOnce::Output`?
565+
566+
As mentioned above, `FnOnce::Output` was stabilized in [#34365](https://github.com/rust-lang/rust/pull/34365) as an alternative to break ecosystem code when a bug was fixed to detect usage of unstable associated items in "type-dependent" associated type paths (e.g. `T::Output` that is not qualified with a trait). This allows the following code on stable:
567+
568+
```rust
569+
fn foo<F, T>()
570+
where
571+
F: FnOnce() -> T,
572+
F::Output: Send, //~ OK
573+
{
574+
}
575+
```
576+
577+
However, the stabilization of the assoicated type did not actually enable new things to be expressed, and instead `FnOnce::Output` just serves as a type alias for an existing type that may already be named.
578+
579+
This is because uniquely to `Fn*` trait bounds (compared to the other `std::ops::*` traits that define `Output` associated types, like `Add`), the associated type for `FnOnce::Output` is always constrained by the parenthesized generic syntax. In other words, given `F: Fn*() -> T`, `F::Output` can always be replaced by some type `T`, since `T` is necessary to complete the parenthesized trait bound syntax[^higher]. In that way, naming a type via the `Output` associated type is not more general or flexible than just naming the type itself:
580+
581+
[^higher]: In fact, the `::Output` syntax doesn't even make it easier to name the return type of a higher-ranked `Fn` trait bound either: https://godbolt.org/z/1rTGhfr9x
582+
583+
```rust
584+
fn foo<F, T>()
585+
where
586+
F: FnOnce() -> T,
587+
F::Output: Send,
588+
// Should just be rewritten like:
589+
T: Send,
590+
{
591+
}
592+
```
593+
594+
Exposing the `Output` type for `AsyncFnOnce` complicates eventually moving onto other desugarings for `async Fn*`. For example, if `AsyncFnOnce` is replaced by a trait alias for `FnOnce`, it may change the meaning of `Output` in a way that would require extending the language or adding a hack into the compiler to preserve its meaning.
595+
596+
Given that expressivity isn't meaningfully impaired by keeping the `Output` associated type as unstable, we do not expect to stabilize this associated type at the same time as async closures, and a stabilization report for the associated type should mention how it affects future possibilities to change the desugaring of `async Fn*`.
597+
564598
# Drawbacks
565599
[drawbacks]: #drawbacks
566600

0 commit comments

Comments
 (0)