The code you posted compiles without problems. Since you declared that the closure should move all the referenced variables into the closure and i32 is Copy
, each thread has its own copy of sum
. So add1 will always return 0. So I guess you meant a version where there is no move
keyword in front of the closure, with an error
error[E0373]: closure may outlive the current function, but it borrows `increment`, which is owned by the current function
--> src/main.rs:9:36
|
9 | handles.push(thread::spawn(|| {
| ^^ may outlive borrowed value `increment`
10 | sum += increment;
| --------- `increment` is borrowed here
|
note: function requires argument type to outlive `'static`
--> src/main.rs:9:22
|
9 | handles.push(thread::spawn(|| {
| ______________________^
10 | | sum += increment;
11 | | }));
| |__________^
help: to force the closure to take ownership of `increment` (and any other referenced variables), use the `move` keyword
|
9 | handles.push(thread::spawn(move || {
| ++++
Which is probably also the reason why you added move
in the first place. So lets look at the error. You said:
Yes, logically it might. Althoug it doesn't as @alice explains in their post. However, the rust compiler has no way to reason about the program in this way. All it has, is the definition of thread::spawn()
. Let's have a look
pub fn spawn<F, T>(f: F) -> JoinHandle<T>where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
The documentation of this function explains:
As you can see in the signature of spawn
there are two constraints on both the closure given to spawn
and its return value, let’s explain them:
- The
'static
constraint means that the closure and its return value must have a lifetime of the whole program execution. The reason for this is that threads can outlive the lifetime they have been created in.Indeed if the thread, and by extension its return value, can outlive their caller, we need to make sure that they will be valid afterwards, and since we can’t know when it will return we need to have them valid as long as possible, that is until the end of the program, hence the 'static
lifetime.
So the compiler cannot reason about what exactly your program does. All it sees, is that the closure needs to live till the end of the program. This is called local reasoning and it makes it easier for you and the compiler to think about programms. Since you only need to look at the function itself to know what contract the closure needs to fulfill, you don't have to know the whole program at once in order to know if the reference outlives the referent. If we didn't have this, one part of the program could influence the correctness of vastly different parts of the program.
There are different solutions to overcome this, but I'll let others explain what these might be.
Btw, a closure having a 'static
bound, i.e.
F: FnOnce() + 'static,
means that it has to either take all its captured values by moving them into the closure, or by reference whit a 'static
lifetime. I.e. the closure needs to own its captured values or be able too keep the reference forever.