Skip to content

Rust incorrectly detects move'd async closure as FnOnce instead of Fn on nightly-2024-02-07 #120957

Closed as not planned

Description

@BigBadE

I tried this code:

#![feature(async_closure)]

use std::future::Future;

fn main() {
    let variable = 12;
    let reference = &variable;
    let example = async move || {
        println!("{}", reference);
    };
    test(example);
}

fn test<T: Future<Output = ()>, F: Fn() -> T>(given: F) {

}

I expected to see this happen: No compiler errors

Instead, this happened:

error[E0277]: expected a `Fn()` closure, found `{async closure@src\main.rs:8:19: 8:32}`
  --> src\main.rs:11:10
   |
11 |     test(example);
   |     ---- ^^^^^^^ expected an `Fn()` closure, found `{async closure@src\main.rs:8:19: 8:32}`
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `Fn<()>` is not implemented for `{async closure@src\main.rs:8:19: 8:32}`
   = note: wrap the `{async closure@src\main.rs:8:19: 8:32}` in a closure with no arguments: `|| { /* code */ }`
   = note: `{async closure@src\main.rs:8:19: 8:32}` implements `FnOnce`, but it must implement `Fn`, which is more general

Meta

rustc --version --verbose:

rustc 1.78.0-nightly (1a648b397 2024-02-11)
binary: rustc
commit-hash: 1a648b397dedc98ada3dd3360f6d661ec2436c56
commit-date: 2024-02-11
host: x86_64-pc-windows-msvc
release: 1.78.0-nightly
LLVM version: 17.0.6

This bug was introduced between nightly-2024-02-06 (where the code builds correctly) and nightly-2024-02-27 (where the error happens), and is still present on nightly-2024-02-11 (the latest when this was released). This bug is likely related to #120886, which was fixed by #120712.

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Feb 12, 2024
  2. Noratrieb commented on Feb 12, 2024

    @Noratrieb
    Member

    @compiler-errors more async closure fun :3

  3. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    WG-asyncWorking group: Async & await
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    WG-asyncWorking group: Async & await
    on Feb 12, 2024
  4. compiler-errors commented on Feb 12, 2024

    @compiler-errors
    Contributor

    @BigBadE: This is somewhat expected behavior after the async closure refactor. Try writing your fn test like:

    fn test(given: impl async Fn() -> T) {}
    

    If this isn't working out in practice, pls share more information about what you're trying to use async closures for.

  5. BigBadE commented on Feb 12, 2024

    @BigBadE
    Author

    @BigBadE: This is somewhat expected behavior after the async closure refactor. Try writing your fn test like:

    fn test(given: impl async Fn() -> T) {}
    

    If this isn't working out in practice, pls share more information about what you're trying to use async closures for.

    Okay, so async closures now are of the AsyncFn trait instead of the Fn trait, which breaks any code that previously expected them to be of the Fn trait. https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_closures.html explains the issue, but I can't help but wonder how async closures used to work when they did implement the Fn trait.

    Either way, the problem is solved as long as this isn't considered a regression.

  6. compiler-errors commented on Feb 12, 2024

    @compiler-errors
    Contributor

    @BigBadE:

    I can't help but wonder how async closures used to work when they did implement the Fn trait.

    Async closures used to just be closures that return async blocks. In other words, another fix for your issue is to just write move || async move {} instead of async move || {}, since before I reworked them, that's literally just what they desugared to.

    I do encourage you to try out "better" async closures; the where clause bounds for your test example function are pretty restrictive in practice, as you can't borrow any state from the closure, which should "just work" with the new async closures.

    I'd love to hear more about how your project(s) are using async closures and whether you're able to express more with the new async Fn() bounds, though -- feel free to shoot me a message on Zulip if you've got more questions or comments.

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

    A-async-closures`async || {}`C-bugCategory: This is a bug.T-compilerRelevant to the compiler 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