AsyncFnMut capturing the environment is not Send

When an async closure mutably capturing the environment is passed as AsyncFnMut, the Send bound is removed.

The following code does not compile, but it works when either removing the Send bound in trait Foo, or when removing the exec call. Can this example be fixed keeping Send and still passing an AsyncFnMut?

trait Foo {
    fn foo() -> impl Future<Output = ()> + Send;
}

async fn exec<F: AsyncFnMut() -> ()>(mut f: F) {
    f().await
}

impl Foo for () {
    async fn foo() {
        let mut counter = 0;
        let mut f = async || {
            counter += 1;
        };

        f().await;
        f().await;
        exec(f).await;
    }
}

I tried to add the Send bound to the Future returned by AsyncFnMut:

#![feature(async_fn_traits)]

trait Foo {
    fn foo() -> impl Future<Output = ()> + Send;
}

async fn exec<F: AsyncFnMut() -> ()>(mut f: F) where for<'a> F::CallRefFuture<'a>: Send {
    f().await
}

impl Foo for () {
    async fn foo() {
        let mut counter = 0;
        let mut f = async || {
            counter += 1;
        };

        f().await;
        f().await;
        exec(f).await;
    }
}

Then, I get this note about "current limitations in the borrow checker":

error[E0597]: `counter` does not live long enough
  --> /tmp/test.rs:14:21
   |
14 |           let mut f = async || {
   |  _____________________^
15 | |             counter += 1;
16 | |         };
   | |_________^ borrowed value does not live long enough
...
20 |           exec(f).await;
   |           ------- argument requires that `counter` is borrowed for `'static`
21 |       }
   |       - `counter` dropped here while still borrowed
   |
note: due to current limitations in the borrow checker, this implies a `'static` lifetime
  --> /tmp/test.rs:7:84
   |
7  | async fn exec<F: AsyncFnMut() -> ()>(mut f: F) where for<'a> F::CallRefFuture<'a>: Send {
   |                                                                                    ^^^^

error: aborting due to 1 previous error

Are implied 'static lifetimes due to current limitations in the borrow checker also the reason why the initial example cannot be compiled?

Here's what I understand: async fn foo() returns a Future, which owns counter.
Within this future, we crate another future via let mut f = async || { counter += 1; }; which borrows counter mutably.
Since that future may be run whenever, this borrow needs to be 'static.
To avoid this, you can pass the counter into the closure as an argument instead of capturing it.

1 Like

The problem is that f has the type MyClosure<'counter> where MyClosure is a name I am giving to the compiler-generated type for the async closure. I am using 'counter to refer to the duration in which the closure is borrowing the 'counter variable.

Let's say you call the closure. Then, you borrow f for some duration we may give the name 'call, and you get a future type that has type <MyClosure<'counter>>::CallRefFuture<'call>. Let's name this future type MyFuture<'call, 'counter>.

The problem is that MyFuture<'call, 'counter> contains a &'call mut MyClosure<'counter>, wihch means that MyFuture<'call, 'counter> does not make sense if 'call is longer than 'counter. Unfortunately, the bound

where for<'a> F::CallRefFuture<'a>: Send

says that MyFuture<'call, 'counter> must implement Send for all possible lifetimes 'call. All possible lifetimes includes lifetimes that are longer than 'counter.

So for example, 'static is one lifetime among all possible lifetimes, so one of the things that it requires is that MyFuture<'static, 'counter>: Send. The problem is that this only works if 'static is not longer than 'counter, so it becomes required that 'counter is 'static.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.