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

Gue*_*OCs 1 smart-pointers move-semantics 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(())
    }));
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

Pet*_*all 6

生成器是一个不稳定的特性,目前只在夜间可用,可以与其他语言(例如JavascriptGoPython)中的生成器或协程进行比较。

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

这些消息可能是一个错误,即这些消息没有正确地进行功能门控,或者对于async脱糖生成的代码与您自己明确编写的代码相比,呈现不同的错误可能太复杂了。

因此,让我们忽略生成器,这对于您的实际问题来说有点小题大做。

你正在x进入一个关闭:

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

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

鉴于您已经说过您无法更改run为接受 an 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(())
    }));
}
Run Code Online (Sandbox Code Playgroud)

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