Skip to content

Tracking Issue for #![feature(async_fn_traits)] #146790

Open

Description

@9999years

Feature gate: #![feature(async_fn_traits)]

RFC: I couldn't find one.

Public API

This is a tracking issue for the methods and associated types of the AsyncFn family of traits:

The traits themselves are stable, but it is not possible to implement them or (critically) write bounds on the returned future on stable.

Steps / History

(Remember to update the S-tracking-* label when checking boxes.)

Unresolved Questions

  • Unknown?

Related:

Footnotes

  1. https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩

Activity

  1. added
    T-libs-api[DEPRECATED; DO NOT USE]
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Sep 19, 2025
  2. 9999years commented on Sep 19, 2025

    @9999years
    ContributorAuthor

    Here's a function you can express with AsyncFnOnce and the FnOnce equivalent, which requires a second generic parameter:

    async fn doggy<F>(succeeded: F)
    where
        F: AsyncFnOnce() + Send + Sync + 'static,
        F::CallOnceFuture: Send + Sync + 'static,
    {
        todo!()
    }
    
    // I don't think this is exactly equivalent, but close...?
    async fn doggy_stable<F, F2>(succeeded: F)
    where
        F: FnOnce() -> F2 + Send + Sync + 'static,
        F2: Future<Output = ()> + Send + Sync + 'static,
    {
        todo!()
    }
  3. Jules-Bertholet commented on Sep 19, 2025

    @Jules-Bertholet
    Contributor

    related: #29625

  4. added
    S-tracking-needs-summaryStatus: It's hard to tell what's been done and what hasn't! Someone should do some investigation.
    T-langRelevant to the language team
    and removed on Sep 19, 2025
  5. workingjubilee commented on Sep 19, 2025

    @workingjubilee
    Member

    Thanks for opening a tracking issue for this! I have linked the obvious PR but any other related PRs are not on my radar. cc @traviscross I believe you may know?

  6. compiler-errors commented on Sep 19, 2025

    @compiler-errors
    Contributor

    I don't think this is exactly equivalent, but close...?

    For FnOnce it's fine, it's close enough and not harmful to write the bound in a two-part T: FnOnce() -> F, F: .... For Fn/FnMut, you'll lose the ability to return a future that borrows from its captures, which is basically the point of async closures in the first place.

  7. compiler-errors commented on Sep 19, 2025

    @compiler-errors
    Contributor

    Not necessarily a comment for this tracking issue itself, but I believe the "idea" is to allow users to write RTN-style T(..): ... bounds to bound the futures returned by async closures, see https://rust-lang.github.io/rfcs/3668-async-closures.html#interaction-with-return-type-notation-naming-the-future-returned-by-calling.

  8. added
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    on Feb 14, 2026
  9. target-san commented on Apr 23, 2026

    @target-san

    @compiler-errors Thing is, RTNs seem to be in "no ETA" state. This stabilization looks like a quite low-hanging fruit which will nevertheless resolve major pain point by allowing

    async fn do_thing<F>(succeeded: F)
    where
        F: AsyncFnOnce(),
        F::CallOnceFuture: Send,
    {
        todo!()
    }

    I'm personally regularly forced to either not use async-like closures at all or build some strange workarounds exactly because I cannot express Send bound on AsyncFn/AsyncFnMut future. This arises extremely often when you write some kind of HTTP+database backend.

  10. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Aug 12, 2026
  11. cztomsik commented on Sep 29, 2026

    @cztomsik

    I've just got hit by this too. I was excited about async closures only to find out I cannot use them with tokio, because I can't restrict F and this is only allowed in nightly:

    type RunFuture<'a> = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>> + Send + 'a>>;
    
    trait Handler: Send {
        fn call<'a>(&'a mut self) -> RunFuture<'a>;
    }
    
    impl<F> Handler for F
    where
        F: AsyncFnMut() -> Result<(), anyhow::Error> + Send,
        for<'a> <F as AsyncFnMut<()>>::CallRefFuture<'a>: Send,
    {
        fn call<'a>(&'a mut self) -> RunFuture<'a> {
            Box::pin(self())
        }
    }
    

    BTW: possible workaround for anyone who ends up here. You can use tokio LocalSet, and start a separate, top-level (and never-ending) Future, along with your axum::serve, and then use select! to wait until the server stops. That way, you can have something like cron scheduler and still be able to use async closures for your handlers, along with env capturing (my motivation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCS-tracking-needs-summaryStatus: It's hard to tell what's been done and what hasn't! Someone should do some investigation.T-langRelevant to the language teamT-libsRelevant to the library team, which will review and decide on the PR/issue.T-typesRelevant to the types 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