rust 因使用发生器而发生移动的误差是什么意思?

uinbv5nw  于 2023-01-26  发布在  其他
关注(0)|答案(2)|浏览(151)

关于发电机我有个问题:

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的闭包中,根本不应该有借用。

omqzjyyz

omqzjyyz1#

Generators是一个不稳定的特性,目前只在nightly中可用,它可以与其他语言(例如Javascript、GoPython)中的生成器或协程相比较。
生成器本质上是状态机,可以使用yield暂停执行,稍后再次恢复,并有可能在每个转换中传递数据。这种模式非常适合异步编程,Rust编译器扩展某些async代码以使用生成器,即使在没有启用夜间特性的情况下您自己无法显式使用它们。
这些消息可能是一个bug,没有正确地进行功能门控,或者对于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来使f可被调用多次。

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,那么你几乎可以肯定一开始就不需要它!

wz8daaqr

wz8daaqr2#

“move occurs due to use in generator”错误似乎在这里有点混淆,但总体问题与生成器无关。
您的run函数接受Fn()函数,而不接受FnOnce()函数。这意味着传递给run的函数必须能够执行多次。鉴于此,您的代码无法按编写的方式工作,因为在将x分配给y时,运行您的函数会占用x的所有权。如果对该函数的第一次调用占用x的所有权,函数第二次运行时应该发生什么?x的所有权在第一次执行时就已经交给了future,两个对象不能拥有相同的值。

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

相关问题