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

Posted

技术标签:

【中文标题】有没有办法让 Rust 闭包只将一些变量移入其中?【英文标题】:Is there a way to have a Rust closure that moves only some variables into it? 【发布时间】:2020-02-15 23:16:27 【问题描述】:

我有一个通用的 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 |_| 
   |                         ^^^^^^^^

这将使&amp;settings 无效,因为第一个循环迭代将在move 闭包中取得settings 的所有权。

使其工作的唯一简单方法是:

Settings 结构复制到每个线程中(在我的实际应用程序中这是相当昂贵的) 在settings周围引入一个Arc,也觉得有点可惜。

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

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

【参考方案2】:

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

是的,这可以用来“规避”引用计数。

我在rayon::scope 的文档中找到了一个答案,该答案正是关于这个问题的:“[从范围内的线程范围内] 访问堆栈数据”。该页面还有一个比这个问题中的伪代码更清晰的示例。

事实证明,您可以:

使用move 闭包,但通过用引用遮蔽它们来引用外部作用域中的变量,因此使用let settings = &amp;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();

【讨论】:

这里是极小的吹毛求疵,但你并没有真正“规避”任何事情,你只是不是引用计数。 @trentcl 谢谢!你当然是对的。 “规避”在这里可能有点草率。我稍微改变了答案中的书写方式,但如果您有更好的建议使其更清晰,请随时提出一个(和/或自己直接编辑答案) Your second answer does not work.

以上是关于有没有办法让 Rust 闭包只将一些变量移入其中?的主要内容,如果未能解决你的问题,请参考以下文章

一起来学rust|简单的mingrep

有没有办法从使用提供的 rust 中的 &str 类型获取函数指针

存储过程返回2个结果数据集 - 有没有办法只将第一个数据集插入变量但仍然显示第二个?

编程范式

有没有办法在 Rust 库中包含二进制或文本文件?

Go的闭包看你犯错,Rust却默默帮你排坑