Move occurs due to use in generator, but for captured variable

I'm having this problem about generators:

use tokio::runtime::Runtime;
use tokio::task::JoinHandle;
use std::sync::Arc;

pub fn run(f: Box<dyn Fn() -> Result<(), ()> + Send>) {
    f();
}

fn main() {
    let x = Arc::new(0);
    run(Box::new(move ||{
        let rt = Runtime::new().unwrap();
        let _t = rt.block_on(async move {
            let y = x;
        });
        Ok(())
    }));
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=de28ecf9e5baf6a017cd6a5230d74d7b

Error:

error[E0507]: cannot move out of `x`, a captured variable in an `Fn` closure
  --> src/main.rs:13:41
   |
10 |       let x = Arc::new(0);
   |           - captured outer variable
...
13 |           let _t = rt.block_on(async move {
   |  _________________________________________^
14 | |             let y = x;
   | |                     -
   | |                     |
   | |                     move occurs because `x` has type `Arc<i32>`, which does not implement the `Copy` trait
   | |                     move occurs due to use in generator
15 | |         });
   | |_________^ move out of `x` occurs here

I don't understand why x is borrowed, if in both the closure and the block, I use move. So x should be moved to rt.block_on's closure. There should be no borrow at all.

When calling a Fn closure, the closure can access its captured variables only by immutable borrowing. This is because Fn::call takes &self, which lets the caller run the closure multiple times (even multiple times concurrently or overlapping).

If you want a closure that can move or consume its captured variables, it must be a FnOnce, which can only be called once.

Alternatively, since your captured variable is an Arc, you can cheaply clone it each time the closure is called (Playground1) (Playground 2). Cloning only borrows the original pointer, and produces a new pointer to the same value, which the closure is then free to move or consume.

shouldn't the compiler prevent me from making the closure move then?

The move keyword is about whether the environment is moved into the closure when it is constructed, while Fn/FnMut/FnOnce is about how the environment is passed to the closure's body when it is called.

It's quite common to want a closure that owns its environment but can be called multiple times (i.e., a move closure that implements FnMut and/or Fn), especially when it uses reference-counted pointers like Arc. See the two playground links in my previous comment, for example.

2 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.