Skip to content

New AsyncFn* traits being #[fundamental] might come with significant forward-compatibility issues #136723

Closed
@steffahn

Description

@steffahn

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 to LendingFn* + 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 {}

(playground)


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.

cc @compiler-errors

@rustbot label T-compiler, T-libs-api, F-async_closure, C-discussion

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-closures`async || {}`C-discussionCategory: Discussion or questions that doesn't represent real issues.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions