Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
904 views
in Technique[技术] by (71.8m points)

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

I have a general struct with settings and an extra variable setting that I want to tune and play around with.

For all possible values in an integer range, I want to start a (scoped) thread with this variable set to that value. Depending on this value, they do slightly different work.

Each of these threads should be able to read the general settings 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) {}

This does not compile:

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 |_| {
   |                         ^^^^^^^^

This would invalidate &settings since the first loop iteration will take ownership of settings in a move closure.

The only easy ways to make it work were:

  • copy the Settings struct into each thread (which in my real application is rather expensive)
  • introduce an Arc around settings, which also feels a bit unfortunate.

Is there a way that we can circumvent reference counting here? Is there a way we can move score into the inner closure while still being allowed to reference settings?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Yes, it is possible to move only one or some variables into a closure (rather than all or none).

Yes, this can be used to "circumvent" reference counting.

I found an answer in the documentation of rayon::scope that turns out to be exactly about this problem: 'Accessing the stack data [from within a scoped threads scope]'. That page also has an example that is clearer than the pseudocode in this question.

It turns out that you can either:

  • Use a move closure but refer to variables in the outer scope by shadowing them with a reference, therefore capturing them by reference rather than by value, using 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();
    
  • Use a normal closure, and only move the required variables by shadowing them inside the closure using 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();
    

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...