When I compile the below example, I get an error from rustc.
use std::thread;
fn main() {
let mut x = 1234;
let thread_1 = thread::spawn(|| {
println!("{}", &x);
});
thread_1.join().unwrap();
}
But when I compile this example, it runs successfully on Rust Playground (rustc stable).
fn main() {
let mut x = 1234;
let thread_1 = thread::spawn(move || {
println!("{}", &x);
});
thread_1.join().unwrap();
}
The difference between the two examples is that the first example doesn't have a move keyword while the second example does have it.
For the first code snippet, I get the following error from rustc.
Compiling playground v0.0.1 (/playground)
warning: variable does not need to be mutable
--> src/main.rs:3:9
|
3 | let mut x = 1234;
| ----^
| |
| help: remove this `mut`
|
= note: `#[warn(unused_mut)]` on by default
error[E0373]: closure may outlive the current function, but it borrows `x`, which is owned by the current function
--> src/main.rs:4:34
|
4 | let thread_1 = thread::spawn(|| {
| ^^ may outlive borrowed value `x`
5 | println!("{}", &x);
| - `x` is borrowed here
|
note: function requires argument type to outlive `'static`
--> src/main.rs:4:20
|
4 | let thread_1 = thread::spawn(|| {
| ____________________^
5 | | println!("{}", &x);
6 | | });
| |______^
help: to force the closure to take ownership of `x` (and any other referenced variables), use the `move` keyword
|
4 | let thread_1 = thread::spawn(move || {
| ^^^^^^^
error: aborting due to previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0373`.
error: could not compile `playground`.
To learn more, run the command again with --verbose.
I expected to see a similar error for the second example, but it runs without errors.. Even if &x is moved to the child thread, I think it shouldn't affect the lifetime of the borrow. Am I missing something here? I'm confused why the second example can compile without errors.
Oh, so in the second example, am I moving the ownership of x to the child thread??
What I thought was that I was moving &x to the child thread in the second example..
In other words your moving a reference to a thread that may outlive the referred-to object. That's why Rust objects; you're attempting to create a potentially-dangling reference.
The way I see it that there is no reference to anything in your main(). There is an x, so that is the only thing that can be moved so that the &x can work in the thread closure.
Variables in a closure are always captured "by name". If you write &x in a closure, x is moved (if a move closure) or borrowed (if a non-move closure). This is the only part of Rust that works this way so it's not surprising you weren't expecting it. (Interestingly, variables captured by reference in a non-move closure actually behave a lot like C++ references in that they are implemented as pointers under the hood but don't require using * or . syntax to access the referent. (Okay, maybe that wasn't as interesting as I at first thought.))
I seem to recall an (accepted?) RFC that would change this slightly, to allow disjoint borrows of structs, so using x.y in a closure would not borrow all of x but just x.y (leaving the other fields of the struct unborrowed). I'm not sure if this would affect move closures or not. Edit: it is RFC #2229, see there for details.
@kornel Isn't x copied, instead of moved, because Rust's primitive integer types implement Copy, i.e. x is still available outside the thread even when moved into a closure?