Rust multithreading and passing values/references

Hello.
So I am new to rust and trying to do some multithread programming in Rust.

With this piece of code:

use std::thread; 

fn mod_prt(mut v: Vec<i32>) -> Vec<i32>{
    v.push(2);
    println!("Inside thread:  {:?} ", v);
    return v;

}

fn main() {

    let mut v:Vec<i32> = Vec::new();
    v.push(1);
    v.push(3);
    v.push(4);
    println!("Outside thread:  {:?} ", v);
}

Everything works as intended and the output is:

Inside thread:  1  2 
Outside thread:  1  2  3  4 

However, I am trying to get the function to works in a separate thread, and this main function definitely would not work:

fn main() {

    let mut v:Vec<i32> = Vec::new();
    v.push(1);
    let handle = thread::spawn(move || {
        v=mod_prt(v);
    });
    handle.join().unwrap();
    v.push(3);
    v.push(4);
    println!("Outside thread:  {:?} ", v);
}

I got the error:

borrow of moved value: `v`

Same thing happens when I tried to use passing by reference.
This code works fine for what it is:

use std::thread; 

fn mod_prt(v: &mut Vec<i32>){
    v.push(2);
    println!("Inside thread:  {:?} ", *v);

}

fn main() {

    let mut v:Vec<i32> = Vec::new();
    v.push(1);
    mod_prt(&mut v);
    v.push(3);
    v.push(4);
    println!("Outside thread:  {:?} ", v);
}

However, this is a no-go:

fn main() {

    let mut v:Vec<i32> = Vec::new();
    v.push(1);
    let handle = thread::spawn(move || {
        mod_prt(&mut v);
    });
    handle.join().unwrap();
    v.push(3);
    v.push(4);
    println!("Outside thread:  {:?} ", v);
}

So yeah. I am definitely missing something.
What is the most easy way to pass a vector (value and reference) to a function, but execute it on a separated thread?
Thank you.
And also, in Rust, does multithreading means multiprocessing? Because it does not work like so in Python, a.k.a even if the program spans multithread, all threads will still be executed by a single processor, which is a bit useless imo.

Yes, Rust's standard library threads are "real" OS threads and there is no equivalent to Python's global interpreter lock, so true parallelism is available.

The problem with your second program is that, with the move keyword, you're specifying that the closure argument to thread::spawn takes ownership of v -- so that it can be used by the new thread -- meaning that the main thread cannot access it afterward. This is a good thing: otherwise your code could concurrently modify v without any kind of synchronization, which could lead to all kinds of nasty and hard-to-debug behavior. To safely share v between threads, you can wrap it into an Arc<Mutex<Vec<i32>>>, then .clone() this object before passing it into the closure. The documentation for std::sync::Mutex has an example of doing this.

Since you're not actually trying to concurrently modify v here, you could also set up a pair of channels (see std::sync::mpsc) and send v first from the main thread to the spawned thread, then back to the main thread.

See also the chapter on concurrency in The Rust Programming Language, especially sections 16.2 and 16.3.

1 Like

I did have a look at it, but, probably because I am new, I am still at lost on how the thread transfer/receiver can be applied to a different function.

Okay, although I am not that confident in my solution, at least it works, so I will mark my own answer until someone finds a better way.

use std::thread;
use std::sync::mpsc;


fn mod_prt(mut v: Vec<i32>)-> Vec<i32>{
    v.push(2);
    println!("Inside thread:  {:?} ", v);
    v

}

fn main() {

    let mut v:Vec<i32> = Vec::new();
    v.push(1);
    //mod_prt(&mut v);
    let (tx, rx) = mpsc::channel();
    let handle = thread::spawn(move || {
        tx.send(mod_prt(v)).unwrap();
    });
    handle.join().unwrap();
    let mut v_new:Vec<i32>= Vec::new();
    for received in rx {
        v_new = received;
    }
    v_new.push(3);
    v_new.push(4);
    println!("Outside thread: {:?}",v_new);

}

You don't need the loop.

fn main() {
    let mut v: Vec<i32> = Vec::new();
    v.push(1);
    
    let (tx, rx) = mpsc::channel();
    let handle = thread::spawn(move || {
        tx.send(mod_prt(v)).unwrap();
    });
    handle.join().unwrap();
    
    let mut v_new = rx.recv().unwrap();
    v_new.push(3);
    v_new.push(4);
    
    println!("Outside thread: {:?}", v_new);
}

Strictly speaking, the handle.join() is not necessary either as the recv call will wait, but it is probably good as it makes sure that panics in the new threads are propagated.

While using a channel generalizes better, for this example of having the thread yield back the captured state when completed, one can use the fact a thread can return a value, that can be obtained from .joining() on it:

use ::std::thread; 

fn mod_prt (mut v: Vec<i32>)
  -> Vec<i32>
{
    v.push(2);
    println!("Inside thread:  {:?} ", v);
    return v;

}

fn main ()
{
    let mut v:Vec<i32> = Vec::new();
    v.push(1);
    let handle = thread::spawn(move /* v */ || {
        let v_thread = v; // Not necessary, but makes things clearer
        /* return */ mod_prt(v_thread)
    });
    /* Do stuff in parallel of the other thread … */
    // Now join to go back to serial behavior, so that `v` can be fetched back.
    v = handle.join().unwrap();
    v.push(3);
    v.push(4);
    println!("Outside thread:  {:?} ", v);
}
8 Likes