Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async closure does not implement FnMut because it captures state from its environment for async closures, while closure returning async block works fine #125247

Closed
peku33 opened this issue May 18, 2024 · 1 comment · Fixed by #125259
Assignees
Labels
C-bug Category: This is a bug. F-async_closure `#![feature(async_closure)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@peku33
Copy link

peku33 commented May 18, 2024

I've returned to one of my projects after a few months of pause. Before I was using rust nigthly ~1.7x (around November 2023). It also worked through whole 2022 and 2021 IIRC. I've moved to new computer and installed rust nightly 1.80 (latest).

After upgrading I noticed that my whole codebase is full of errors around async move |foo| capturing something from environment.
error: async closure does not implement `FnMut` because it captures state from its environment. However if I change async |_| { ... } to |_| async { ... } it builds and works like previosly.

Minimal example below.

#![feature(async_closure)]

use futures::StreamExt;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let test = Test {};

    test.async_with_async_closure().await;
}

struct Test {}
impl Test {
    // This is how it used to work, but now it does not compile
    async fn async_with_async_closure(&self) {
        futures::stream::repeat(())
            .take(5)
            .for_each(async |_item| { // the difference is here
                self.sync_method();
            }).await;
    }
    
    // This is the workaround
    async fn async_with_async_block(&self) {
        futures::stream::repeat(())
            .take(5)
            .for_each(|_item| async { // the difference is here
                self.sync_method();
            }).await;
    }

    fn sync_method(&self) {
        println!("i am being called");
    }
}

The error for first method is:

   Compiling playground v0.0.1 (/playground)
error: async closure does not implement `FnMut` because it captures state from its environment
    --> src/main.rs:17:23
     |
17   |             .for_each(async |_item| {
     |              -------- ^^^^^^^^^^^^^
     |              |
     |              required by a bound introduced by this call
     |
note: required by a bound in `futures::StreamExt::for_each`
    --> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/stream/stream/mod.rs:1093:12
     |
1091 |     fn for_each<Fut, F>(self, f: F) -> ForEach<Self, Fut, F>
     |        -------- required by a bound in this associated function
1092 |     where
1093 |         F: FnMut(Self::Item) -> Fut,
     |            ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `StreamExt::for_each`

error: async closure does not implement `FnMut` because it captures state from its environment
  --> src/main.rs:17:23
   |
17 |             .for_each(async |_item| {
   |                       ^^^^^^^^^^^^^
   |
   = note: required for `ForEach<Take<Repeat<()>>, {async closure body@src/main.rs:17:37: 19:14}, {async [email protected]:17:23}>` to implement `futures::Future`
   = note: required for `ForEach<Take<Repeat<()>>, {async closure body@src/main.rs:17:37: 19:14}, {async [email protected]:17:23}>` to implement `std::future::IntoFuture`
   = note: the full name for the type has been written to '/playground/target/debug/deps/playground-71f1e6caf9dc6471.long-type-11561845803583376377.txt'
   = note: consider using `--verbose` to print the full type name to the console
   = note: the full name for the type has been written to '/playground/target/debug/deps/playground-71f1e6caf9dc6471.long-type-11561845803583376377.txt'
   = note: consider using `--verbose` to print the full type name to the console

error: could not compile `playground` (bin "playground") due to 2 previous errors

i booted my very old PC to check, this is definately not the last version this worked fine:
rustc 1.70.0-nightly (ab654863c 2023-03-15)
I definately does not work with:
rustc 1.80.0-nightly (8387315ab 2024-05-14)

@peku33 peku33 added C-bug Category: This is a bug. regression-untriaged Untriaged performance or correctness regression. labels May 18, 2024
@rustbot rustbot added I-prioritize Issue: Indicates that prioritization has been requested for this issue. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels May 18, 2024
@compiler-errors compiler-errors self-assigned this May 18, 2024
@compiler-errors compiler-errors added F-async_closure `#![feature(async_closure)]` and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. regression-untriaged Untriaged performance or correctness regression. labels May 18, 2024
@compiler-errors
Copy link
Member

Minimal:

#![feature(async_closure)]

fn main() {}

fn needs_fn_mut<T>(x: impl FnMut() -> T) {}

fn hello(x: &Ty) {
    needs_fn_mut(async || { x.hello(); });
}

struct Ty;
impl Ty {
    fn hello(&self) {}
}

This should be possible now to support due to changes in #123660. I'll put up a fix soon.

@fmease fmease added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels May 18, 2024
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue May 22, 2024
… r=oli-obk

An async closure may implement `FnMut`/`Fn` if it has no self-borrows

There's no reason that async closures may not implement `FnMut` or `Fn` if they don't actually borrow anything with the closure's env lifetime. Specifically, rust-lang#123660 made it so that we don't always need to borrow captures from the closure's env.

See the doc comment on `should_reborrow_from_env_of_parent_coroutine_closure`:

https://github.com/rust-lang/rust/blob/c00957a3e269219413041a4e3565f33b1f9d0779/compiler/rustc_hir_typeck/src/upvar.rs#L1777-L1823

If there are no such borrows, then we are free to implement `FnMut` and `Fn` as permitted by our closure's inferred `ClosureKind`.

As far as I can tell, this change makes `async || {}` work in precisely the set of places they used to work before rust-lang#120361.
Fixes rust-lang#125247.

r? oli-obk
@bors bors closed this as completed in 44c7a2d May 22, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this issue May 22, 2024
Rollup merge of rust-lang#125259 - compiler-errors:fn-mut-as-a-treat, r=oli-obk

An async closure may implement `FnMut`/`Fn` if it has no self-borrows

There's no reason that async closures may not implement `FnMut` or `Fn` if they don't actually borrow anything with the closure's env lifetime. Specifically, rust-lang#123660 made it so that we don't always need to borrow captures from the closure's env.

See the doc comment on `should_reborrow_from_env_of_parent_coroutine_closure`:

https://github.com/rust-lang/rust/blob/c00957a3e269219413041a4e3565f33b1f9d0779/compiler/rustc_hir_typeck/src/upvar.rs#L1777-L1823

If there are no such borrows, then we are free to implement `FnMut` and `Fn` as permitted by our closure's inferred `ClosureKind`.

As far as I can tell, this change makes `async || {}` work in precisely the set of places they used to work before rust-lang#120361.
Fixes rust-lang#125247.

r? oli-obk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. F-async_closure `#![feature(async_closure)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants