Description
As far as I understand the current design of AsyncFn*
traits allows them to become somewhat of a “sugar” for a more generalized approach in the future. Quoting from the RFC:
Changing the underlying definition to use
LendingFn*
As mentioned above,
async Fn*()
trait bounds can be adjusted to desugar toLendingFn*
+FnOnce
trait bounds, using associated-type-bounds like:where F: async Fn() -> i32 // desugars to where F: for<'s> LendingFn<LendingOutput<'s>: Future<Output = i32>> + FnOnce<Output: Future<Output = i32>>This should be doable in a way that does not affect existing code, but remain blocked on improvements to higher-ranked trait bounds around GATs. Any changes along these lines remain implementation details unless we decide separately to stabilize more user-observable aspects of the
AsyncFn*
trait, which is not likely to happen soon.
In particular, the case for AsyncFnOnce
seems pretty clear. It’s a future possibility (which is IMO very desirable) that AsyncFnOnce(Args…) -> R
might be automatically implemented for any implementor of FnOnce(Args…) -> F
that returns some F: Future<Output = R>
; either by turning it into a sort of “alias” (in a way that avoids the shortcomings mentioned in the RFC) or by finding a way to re-structure the blanket impls.
In particular, IMO it’s a very relevant future possibility that Box<dyn FnOnce(Args…) -> Pin<Box<dyn Future<Output = R> [+ Send] [+Sync]>>
could become an implementor of AsyncFnOnce(Args…) -> R
in the future.
But #[fundamental]
kills this possibility, as the following code compiles successfully, powered by the negative reasoning that #[fundamental]
provides:
use std::pin::Pin;
use std::future::Future;
type BoxedFnOnceReturningBoxFuture = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()>>>>;
trait MyTraitNoOverlap {}
impl MyTraitNoOverlap for BoxedFnOnceReturningBoxFuture {}
impl<F: AsyncFnOnce()> MyTraitNoOverlap for F {}
I was unable to find any prior discussion about the value of why these traits (AsyncFn
, AsyncFnMut
, AsyncFnOnce
) are marked #[fundamental]
in the first place. I can imagine it’s perhaps by analogy to Fn
, FnMut
, FnOnce
being marked as such. But this decision is an important trade-off that should be considered.
As far as I can tell, there should be no issue in simply removing the #[fundamental]
markers from AsyncFn
, AsyncFnMut
, AsyncFnOnce
before they’re stabilized in 1.85
. Testing this locally, std
still compiles find, and UI tests pass.
It should always be backwards-compatible to add back the #[fundamental]
marker later.
@rustbot label T-compiler, T-libs-api, F-async_closure, C-discussion