ilmoi
ilmoi

Reputation: 2534

Moving non-Copy variable into async closure: captured variable cannot escape `FnMut` closure body

I'm trying to get clokwerk to schedule an asynchronous function to run every X seconds.

The docs show this example:

// Create a new scheduler
let mut scheduler = AsyncScheduler::new();
// Add some tasks to it
scheduler
    .every(10.minutes())
        .plus(30.seconds())
    .run(|| async { println!("Simplest is just using an async block"); });
// Spawn a task to run it forever
tokio::spawn(async move {
  loop {
    scheduler.run_pending().await;
    tokio::time::sleep(Duration::from_millis(100)).await;
  }
});

My initial attempt:

    let config2 = // define a Config struct, Config
    let pg_pool2 = // get a sqlx connection pool, Pool<Postgres>

    //I assume I need shared references so I use Arc
    let pg_pool2 = Arc::new(pg_pool2);
    let config2 = Arc::new(config2);

    let mut scheduler = AsyncScheduler::new();

    scheduler.every(5.seconds()).run(|| async {
        println!("working!");
        pull_from_main(pg_pool2.clone(), config2.clone()).await;
    });

    tokio::spawn(async move {
        loop {
            scheduler.run_pending().await;
            tokio::time::sleep(Duration::from_millis(100)).await;
        }
    });

The compiler complains that pg_pool2 and config2 may outlive the borrowed value, and suggests to add move. Fair. Let's try that.

My second attempt:

    //rest the same
    scheduler.every(5.seconds()).run(move || async {
    //rest the same

This time I get back an error I can't decipher on my own:

error: captured variable cannot escape `FnMut` closure body
  --> src/main.rs:80:46
   |
75 |       let pg_pool2 = Arc::new(pg_pool2);
   |           -------- variable defined here
...
80 |       scheduler.every(5.seconds()).run(move || async {
   |  ____________________________________________-_^
   | |                                            |
   | |                                            inferred to be a `FnMut` closure
81 | |         println!("working!");
82 | |         pull_from_main(pg_pool2.clone(), config2.clone()).await;
   | |                        -------- variable captured here
83 | |     });
   | |_____^ returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

Can someone help me understand what's wrong and how to fix it?

Note: I've seen this question asked before, but I'm struggling to apply the answers to my case.

I'm also a beginner so maybe they do apply, but I don't see the connection:)

Upvotes: 8

Views: 3497

Answers (1)

Svetlin Zarev
Svetlin Zarev

Reputation: 15683

In order to understand what's going on, I'll reformat the code a bit in order to make it more clear and explicit:

Your original code:

  scheduler
    .every(5.seconds())
    .run(move || async {
        do_something(arc.clone());
    });

Is equivalent to:

  scheduler
    .every(5.seconds())
    .run(move || {
       return async {
          do_something(arc.clone());
       }
    });

So you create a closure, which is of type FnMut (and which returns a type that implements Future). That means that your closure can be called multiple times, and each invocation should produce a new future. But return async{} moves your Arc out of the closure, which means that it can be called only once. Imagine that you have a 10$ note in your wallet. If you take it out and spend it, then you will not be able to take it out and spend it for a second time, because it's simply not there anymore.

So how can we solve that ? It's pretty easy actually - you have to clone your Arc before moving it to the async block. Thus you will move only the clone:

    let arc = Arc::new(whatever);   
    scheduler
        .every(5.seconds())
        .run(move || {
             // Clone the arc and move the clone!!! 
             // The original arc will remain in the closure, 
             // so it can be called multiple times.
            let x = arc.clone(); 
            async move { 
                do_something(x);
            }
        });

Upvotes: 18

Related Questions