Using move in threads

I'm a little confused about using move in threads. I have tried two versions of the same program that basically sends data from a spawned thread to the main thread using mpsc channels. One of them requires move in the spawned thread and the other doesn't.

The first one throws error, if I don't use move, saying the Sender cannot be safely passed to threads. The code is below and I've added move to make it work.

fn main(){
    let (tx,rx) = mpsc::channel();

    let handle = thread::spawn(move || {
        tx.send("From thread".to_string()).unwrap();
    });

    handle.join().unwrap();
    let out = rx.recv().unwrap();
    println!("output: {}", out);
}

The second one is almost the same with the difference that I have called another function inside the thread to pass the data using the mpsc Sender tx.

fn main(){
    let (tx, rx) = mpsc::channel();

    let handle = thread::spawn( ||{
        send_function(tx);
    });

    handle.join().unwrap();

    let out = rx.recv().unwrap();
    println!("out : {}", out);
}

fn send_function(a: mpsc::Sender<String>){
    a.send("Hello".to_string()).unwrap();
}

This one works without move. Why?

A part of the closure's context will be automatically moved into the closure if that part is consumed inside the closure. If some context is only borrowed inside the closure, the closure will borrow it by default. This is decided separately for each variable of the captured context. The move keyword forces all the context to be moved into the closure.

In the first version, the only use of tx inside the closure is to call tx.send. This function accepts &self, so tx doesn't have to be moved inside the closure, and so it's not moved by default.

In the second version, tx is used as an argument to send_function. The function consumes Sender by value, so that part of the context has to be moved inside the closure (if it's not moved, the closure won't be able to execute at all, so the compiler doesn't have a choice). The move keyword doesn't do anything here because all parts of the context are already moved inside the closure.

Consider this example:

fn send_function(a: &mpsc::Sender<String>){
    a.send("Hello".to_string()).unwrap();
}

let handle = thread::spawn(move || {
    send_function(&tx);
});

Now send_function takes a reference to Sender (note the argument type), so tx is borrowed by the closure by default, and so in this case the move keyword is required for the same reasons as in your first version.

4 Likes

Thanks @Riateche. Tried out using reference to Sender. Worked like you explained. Also tried using tx after the thread spawn.

7  |     let handle = thread::spawn( ||{
   |                                 -- value moved into closure here
8  |         send_function(tx);
   |                       -- variable moved due to use in closure

You are right. It is moved in the closure.

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.