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:
Settings
struct into each thread (which in my real application is rather expensive)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
?
Moving closures Rust has a second type of closure, called a moving closure. Moving closures are indicated using the move keyword (e.g., move || x * x ). The difference between a moving closure and an ordinary closure is that a moving closure always takes ownership of all variables that it uses.
A closure type is approximately equivalent to a struct which contains the captured variables. For instance, the following closure: #![ allow(unused)] fn main() { fn f<F : FnOnce() -> String> (g: F) { println!
Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then call the closure elsewhere to evaluate it in a different context. Unlike functions, closures can capture values from the scope in which they're defined.
When doing assignments ( let x = y ) or passing function arguments by value ( foo(x) ), the ownership of the resources is transferred. In Rust-speak, this is known as a move. After moving resources, the previous owner can no longer be used.
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();
The closure! macro gives the ability to selectively reference, move, or clone variables into a closure.
Example taken from the docs:
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| {
...
});
Variables that are captured but not listed default to being moved.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With