“由于在生成器中使用而发生移动”错误在 Rust 中意味着什么?

What does `move occurs due to use in generator` error mean in Rust?

我遇到了关于生成器的问题:

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[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

我不明白为什么要借用x,如果在闭包和块中,我都使用move。所以 x 应该移到 rt.block_on 的闭包处。根本就不能借。

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

生成器本质上是状态机,可以使用 yield 暂停执行并稍后再次恢复,并有可能在每次转换中传递数据。这种模式非常适合异步编程,Rust 编译器扩展了某些 async 代码以使用生成器,即使你不能在没有启用 nightly 功能的情况下自己明确地使用它们。

这可能是一个错误,因为这些消息没有正确地进行功能门控,或者它可能太复杂了,无法为从 async 脱糖生成的代码显示与您明确显示的代码不同的错误自己写的。

所以让我们忽略生成器,对于您的实际问题,它们有点转移注意力。

您正在 x 移动到闭包中:

let x = Arc::new(0);
run(Box::new(move ||{
    // 'move' closure uses x so x is moved
}));

然后闭包再次将 x 移动到 async 块中。问题是 run 的签名接受一个 Fn 闭包——一个不能修改其环境但 可以 被多次调用的闭包。但是,您提供的闭包在第一次调用时将 x 移动到 async 块中,因此第二次调用它是无效的。

鉴于您已经说过您无法更改 run 以接受 FnOnce,您需要通过阻止 f 移动来多次调用 x ].您可以通过克隆它来做到这一点:

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(())
    }));
}

Arc 的整个设计都是关于廉价克隆的。它很便宜,因为它只复制指向您的数据的指针并增加引用计数,这就是它知道何时可以安全释放保存其数据的内存的方式。如果您从不克隆 Arc 那么您几乎肯定一开始就不需要它!

“由于在生成器中使用而发生移动”错误在这里似乎有点混乱,但总体问题与生成器无关。

您的 run 函数接受 Fn() 函数,而不是 FnOnce() 函数。这意味着传递给 run 的函数必须能够执行多次。鉴于此,您的代码无法按照编写的方式运行,因为 运行 您的函数在将其分配给 y 时取得了 x 的所有权。如果第一次调用该函数获得 x 的所有权,那么该函数第二次运行时应该发生什么? x 的所有权在它第一次执行时就已经被赋予了未来,并且两个事物不能拥有相同的价值。同样的问题也会发生在

run(Box::new(move ||{
    let y = x;
    Ok(())
}));

既然你有一个 Arc 我猜你已经感觉到了这一点,但如果没有更多的逻辑就不会发生。由于您使用的是 Arc,因此您需要在函数的开头克隆 x,以便每次函数运行时,它都会获得自己的 Arc 对象来处理和操作。

fn main() {
    let x = Arc::new(0);
    run(Box::new(move ||{

        // Make a new Arc that can be given to the promise without needing
        // to worry about what future calls to this callback willl do.
        let x = Arc::clone(&x);

        let rt = Runtime::new().unwrap();
        let _t = rt.block_on(async move {
            let y = x;
        });
        Ok(())
    }));
}