有没有一种方法可以让Rust闭包只移动一些变量?

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

我有一个带有设置的通用struct和一个额外的变量设置,我想调整和尝试一下。
对于一个整数范围内所有可能的值,我想用这个变量设置为那个值来启动一个(有作用域的)线程。根据这个值,它们做的工作略有不同。
这些线程中的每一个都应该能够读取常规设置结构。

use crossbeam; // 0.7.3

struct Settings {
    // ... many fields
}

const MAX_FEASIBLE_SCORE: u8 = 10;

fn example(settings: Settings) {
    crossbeam::scope(|scope| {
        for score in 0..MAX_FEASIBLE_SCORE {
            scope.spawn(|_| {
                let work_result = do_cool_computation(&settings, score);
                println!("{:?}", work_result);
            });
        }
    })
    .unwrap();
}

fn do_cool_computation(_: &Settings, _: u8) {}

这不会编译:

error[E0373]: closure may outlive the current function, but it borrows `score`, which is owned by the current function
  --> src/lib.rs:12:25
   |
10 |     crossbeam::scope(|scope| {
   |                       ----- has type `&crossbeam_utils::thread::Scope<'1>`
11 |         for score in 0..MAX_FEASIBLE_SCORE {
12 |             scope.spawn(|_| {
   |                         ^^^ may outlive borrowed value `score`
13 |                 let work_result = do_cool_computation(&settings, score);
   |                                                                  ----- `score` is borrowed here
   |
note: function requires argument type to outlive `'1`
  --> src/lib.rs:12:13
   |
12 | /             scope.spawn(|_| {
13 | |                 let work_result = do_cool_computation(&settings, score);
14 | |                 println!("{:?}", work_result);
15 | |             });
   | |______________^
help: to force the closure to take ownership of `score` (and any other referenced variables), use the `move` keyword
   |
12 |             scope.spawn(move |_| {
   |                         ^^^^^^^^

这将使&settings无效,因为第一次循环迭代将取得move闭包中settings的所有权。
唯一简单的方法是:

  • Settings结构体复制到每个线程中(这在我的真实的应用程序中相当昂贵)
  • 围绕settings引入一个Arc,这也让人感觉有点遗憾。

有没有办法可以绕过这里的引用计数?有没有办法可以把score移到内部闭包中,同时仍然允许引用settings

oogrdqng

oogrdqng1#

是的,可以只将一个或一些变量移到闭包中(而不是全部或一个都不移)。
是的,这可以用来“规避”引用计数。
我在rayon::scope的文档中找到了一个答案,结果证明它正是关于这个问题的:“访问堆栈数据[从作用域线程作用域内]”。该页还有一个示例,比此问题中的伪代码更清楚。
事实证明,您可以按如下方式修复此问题:
使用move闭包,但通过使用引用隐藏外部作用域中的变量来引用它们,因此使用let settings = &settings通过引用而不是通过值来捕获它们:

crossbeam::scope(|scope| {
let settings = &settings; // refer to outer variable by reference
for score in 0..MAX_FEASIBLE_SCORE {
scope.spawn(move |_| {
let work_result = do_cool_computation(settings, score);
println!("{:?}", work_result);
});
}
})
.unwrap();

Playground所以在这里,我们移动了'all used variables',但事先将settings转换为引用,所以它是借用的。(教育性地:我们“移动引用”,但这正是“借用”的含义。)
还有第二种可能性:借用所有变量(不移动任何内容)。
这种可能性在很多上下文中都有效,但在这里不行(因为在这里,我们 * 必须 * 通过值来捕获score,因为在for循环的下一次迭代中它将不在那里,传递给scope.spawn的闭包将比它更有效)。
摘自rayon::scope的文档

use rayon;

fn main() {
let ok: Vec<i32> = vec![1, 2, 3];
rayon::scope(|s| {
    let bad: Vec<i32> = vec![4, 5, 6];
    s.spawn(|_| {
        // Transfer ownership of `bad` into a local variable (also named `bad`).
        // This will force the closure to take ownership of `bad` from the environment.
        let bad = bad;
        println!("ok: {:?}", ok); // `ok` is only borrowed.
        println!("bad: {:?}", bad); // refers to our local variable, above.
    });

    s.spawn(|_| println!("ok: {:?}", ok)); // we too can borrow `ok`
});
}

Playground

eqoofvh9

eqoofvh92#

closure! macro from the closure crate提供了选择性地引用、移动或克隆变量到闭包中的能力。
示例摘自文档:

use closure::closure;

let string = "move".to_string();
let x = 10;
let mut y = 20;
let rc = Rc::new(5);

let closure = closure!(move string, ref x, ref mut y, clone rc, |arg: i32| {
    ...
});

已捕获但未列出的变量默认为被移动。

相关问题