首页 > 解决方案 > 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's 关闭。根本不应该借钱。

标签: rustsmart-pointersmove-semantics

解决方案


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

生成器本质上是状态机,可以使用暂停执行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闭包的签名——一个不能修改其环境但可能被多次调用的闭包。但是,您提供的闭包在第一次调用时会移动xasync块中,因此第二次调用它是无效的。

鉴于您已经说过您不能更改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那么您几乎可以肯定一开始就不需要它!


推荐阅读