有没有办法让 Rust 闭包只将一些变量移入其中?

Is there a way to have a Rust closure that moves only some variables into it?

我有一个通用的 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 的所有权。

让它发挥作用的唯一简单方法是:

有什么办法可以绕过这里的引用计数吗?有没有一种方法可以将 score 移动到内部闭包中,同时仍然允许引用 settings?

是的,可以只将一个或一些变量移动到闭包中(而不是全部或none)。

是的,这可以用于"circumvent"引用计数。

我在 rayon::scope 的文档中找到了一个答案,结果正是关于这个问题:'Accessing the stack data [from within a scoped threads 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();
    
  • 使用普通闭包,仅通过使用 let score = score:

    将所需变量隐藏在闭包内来移动它们
    crossbeam::scope(|scope| {
        for score in 0..MAX_FEASIBLE_SCORE {
            scope.spawn(|_| {
                let score = score; // capture only score
                let work_result = do_cool_computation(&settings, score);
                println!("{:?}", work_result);
            });
        }
    })
    .unwrap();
    

closure! macro 提供了有选择地引用、移动或克隆变量到闭包中的能力。

取自文档的示例:

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| {
    ...
});

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