Value moved [std::thread⁠::JoinHandle::join]

I am a beginner.
error[E0507]: cannot move out of a shared reference. occurs within a method that returns a tuple of average and total values in two threads. What code is desired to solve this in a simple way?

    pub fn calc_avg_and_sum_use_mutex(values: Vec<u64>) -> Result<(u64, u64)> {
        let params = Arc::new(Mutex::<Vec<u64>>::new(values));
        let mut handles: Vec<JoinHandle<u64>> = Vec::with_capacity(2);

        let builder = Builder::new().name("avg".to_owned()).stack_size(1024 * 3);
        let clone_param = Arc::clone(&params);
        handles.push(builder.spawn(move || Self::calc_avg(clone_param))?);

        let builder = Builder::new().name("sum".to_owned()).stack_size(1024 * 3);
        let clone_param = Arc::clone(&params);
        handles.push(builder.spawn(move || Self::calc_sum(clone_param))?);

        let avg = handles.get(0).unwrap().join().unwrap(); 
        let sum = handles.get(1).unwrap().join().unwrap();
        Ok((avg, sum))
    }

error[E0507]: cannot move out of a shared reference

43   |         let avg = handles.get(0).unwrap().join().unwrap(); 
     |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------
     |                   |                               |
     |                   |                               value moved due to this method call
     |                   move occurs because value has type `std::thread::JoinHandle<u64>`, which does not implement the `Copy` trait

Might be simplest to just keep the two handles in two separate variables here ^^

Otherwise.. assuming ownership of a Vec's item is possible through various methods. To name just a few, you could use use Vec::remove or ::pop methods, or you could use a by-value iterator either via explicit .into_iter() or implicitly in a for loop such as for handle in handles { … }

2 Likes

get only gives you a reference to the items in a Vec, so you can't call methods that require ownership like join.

Since there's only two handles, you can just assign them to their own variables.

pub fn calc_avg_and_sum_use_mutex(values: Vec<u64>) -> Result<(u64, u64)> {
    let params = Arc::new(Mutex::new(values));

    let builder = Builder::new().name("avg".to_owned()).stack_size(1024 * 3);
    let clone_param = Arc::clone(&params);
    let handle_avg = builder.spawn(move || Self::calc_avg(clone_param))?;

    let builder = Builder::new().name("sum".to_owned()).stack_size(1024 * 3);
    let clone_param = Arc::clone(&params);
    let handle_sum = builder.spawn(move || Self::calc_sum(clone_param))?;

    let avg = handle_avg.join().unwrap();
    let sum = handle_sum.join().unwrap();
    Ok((avg, sum))
}

If you have to use a Vec, like if you're making a bunch of threads in a loop, you can call pop or into_iter to obtain ownership of the items in the Vec.

// The handles pop in reverse order, so sum is first.
let sum = handles.pop().unwrap().join().unwrap();
let avg = handles.pop().unwrap().join().unwrap();
let mut handles_iter = handles.into_iter();
let avg = handles_iter.next().unwrap().join().unwrap();
let sum = handles_iter.next().unwrap().join().unwrap();

However, your code right now isn't going to run in parallel. It'll run on separate threads, but it'll run sequentially. You can fix this by removing the Mutex and updating the calc functions to match.

You can also use thread::scope to avoid needing the Arc.

pub fn calc_avg_and_sum_use_mutex(values: &[u64]) -> Result<(u64, u64)> {
    std::thread::scope(|scope| {
        let builder = Builder::new().name("avg".to_owned()).stack_size(1024 * 3);
        let handle_avg = builder.spawn_scoped(scope, move || Self::calc_avg(values))?;

        let builder = Builder::new().name("sum".to_owned()).stack_size(1024 * 3);
        let handle_sum = builder.spawn_scoped(scope, move || Self::calc_sum(values))?;

        Ok((handle_avg.join().unwrap(), handle_sum.join().unwrap()))
    })
}
2 Likes

The main problem is that the ownership of the value held in mut Vec can be changed by other methods.
Therefore, the solution of using two variables to avoid owning the JoinHandle in Vec is very simple and easy to understand.
Thanks for your reply.
I will try to find a solution to this problem.

I didn't know about the thread::scope solution, and now I clearly understand the need to use pop and next.
Thank you very much for your detailed explanation.
Thank you for sharing your great knowledge.

1 Like