Question on moving a reference to a child thread

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. :dizzy_face:

That thread you have spawned there could run forever. An infinite lifetime.

But that x only exists for the time the function it is created in take to run.

So, you had better move the 'x' into the thread where it can live forever as well.

As the compiler says closure may outlive the current function, but it borrows x`, which is owned by the current function'

Now, you could ask "My 'x' is in main which will end the program anyway when it returns, so why is this a problem?"

Or you ask "I have a join there so main has to live as long as my thread and so does 'x', so why is this a problem"

I have no idea except the notion that the borrow checker is not going to analyse your program to that extent.

1 Like

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.

1 Like

Ah, I see.

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.

1 Like

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.

2 Likes

You're moving x to the closure. Closure is then moved to a thread.

Closures are combination of data & code. In the first example you have closure with no data, which borrows x from main thread's stack.

in the second example you have closure with x in its data, so the code of the closure borrows from closure's data, not main.

@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?

For copyable types move is implemented via copy, but it's a separate issue that doesn't affect semantics of references and move closures.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.