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(())
}));
}
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.
Generators are an unstable feature, currently only available in nightly, which can be compared to generators or coroutines in other languages (e.g. Javascript, Go, Python).
Generators are essentially state machines that can pause execution using yield
and later be resumed again, with the possibility of passing data in each transition. This pattern is ideal for asynchronous programming and the Rust compiler expands certain async
code to use generators, even though you can't explicitly use them yourself without the nightly feature enabled.
It is possible that it is a bug that these messages are not properly feature-gated, or it may just have been too complicated to present different errors for code generated from async
desugaring than for code you explicitly wrote yourself.
So let's ignore generators, which are a bit of a red herring for your actual problem.
You are moving x
into a closure:
let x = Arc::new(0);
run(Box::new(move ||{
// 'move' closure uses x so x is moved
}));
And then the closure moves x
again into an async
block. The problem is that the signature of run
accepts a Fn
closure - a closure that cannot modify its environment but may be called multiple times. However the closure you have provided moves x
into the async
block the first time it's called, so it would be invalid to call it a second time.
Given you have said that you can't change run
to accept an FnOnce
, you need to make f
callable multiple times by preventing it from moving x
. You can do that by cloning it:
fn main() {
let x = Arc::new(0);
run(Box::new(move || {
let rt = Runtime::new().unwrap();
// each time the closure is called, it will pass a clone of the Arc
// into the task, so it can be called more than once
let x = x.clone();
let _t = rt.block_on(async move {
let y = x;
});
Ok(())
}));
}
The whole design of Arc
is about cheap cloning. It's cheap because it only copies the pointer to your data and increments the reference count, which is how it knows when it is safe to free the memory of the holding its data. If you are never cloning an Arc
then you almost certainly didn't need it in the first place!