Sending to async method seems to lead to an impossible move

I have the following simplified sample producing a problem with moving a clone of a string to a closure. Somehow my usual cloning, Arc/Mutex use did not help here either. So I am a bit puzzled what could be a possible resolution here. Any pointers on further reading etc?

use futures::Future;

struct A {
    v:String,
}

impl A {
    pub async fn m(&self) {
    let cl=self.v.clone();
    b(|s| async move {
        let cl2=cl.clone();
        println!("Hello{}{}",cl2,s);
    });
}
}

#[tokio::main]
async fn main() {
    let a=A { v: String::from(" ") };
    a.m().await;
    println!("Later use: HELLO{}WORLD!",a.v);
}

async fn b<F,G>(f:F) 
where F: Fn(String) -> G + Send + 'static + Sync + Clone,
      G: Future<Output=()> + Send + 'static,
{
    f(String::from("World!")).await
}

The error message:

   Compiling playground v0.0.1 (/playground)
error[E0507]: cannot move out of `cl`, a captured variable in an `Fn` closure
  --> src/main.rs:11:22
   |
10 |       let cl=self.v.clone();
   |           -- captured outer variable
11 |       b(|s| async move {
   |  _______---____________^
   | |       |
   | |       captured by this `Fn` closure
12 | |         let cl2=cl.clone();
   | |                 --
   | |                 |
   | |                 variable moved due to use in generator
   | |                 move occurs because `cl` has type `String`, which does not implement the `Copy` trait
13 | |         println!("Hello{}{}",cl2,s);
14 | |     });
   | |_____^ move out of `cl` occurs here

For more information about this error, try `rustc --explain E0507`.
error: could not compile `playground` due to previous error

It seems that you want FnOnce(), not Fn() for the trait bound. (You're also missing an .await, but that's irrelevant from the perspective of this error.) Playground.

The trait bound comes from a lib unfortunately, so I cannot change that. The lib requires an Fn as it plans to call my method multiple times in a background thread.

In this case, you have to move the original string into the closure, clone it outside the async block, then move the clone into the block:

impl A {
    pub async fn m(&self) {
        let cl = self.v.clone();
        b(move |s| {
            let cl2 = cl.clone();
            async move {
                println!("Hello{}{}", cl2, s);
            }
        }).await;
    }
}

Playground

2 Likes

Thank you! This is definitely working now I am just puzzled why... I would not even have guessed that this would be ok as an async function.

Which part did you think was unsuitable for an async function?

I thought that you have had to do async next to the || to make the closure async.

No, it's not "making the closure async". That's merely an async block inside a perfectly regular closure. (The type annotations of a closure are only of the form |args: Types| -> RetType; everything else beyond that is part of the body/return value.)

"Async" from the point of the type system isn't really special, it just means that the expression has type impl Future<Output = …>, and closures have no async declaration. (A proper async |…| closure form is being developed on nightly, if one can believe the compiler.)

1 Like

Thanks for the explanation, it definitely helps. Not to mention the github issue linked by rustc when compiling your code in playground.