Borrowing multiple thread handles, outside of for loop that instantiates them

Hello, I am having a typical challenge with borrowing in Rust.

I want to use a for loop to spin up a couple threads. I want to wait on all threads in the outer function that creates them.

To accomplish this, I tried creating a Vec<JoinHandle<()>> to hold the handles.

pub fn test_thread() {
  let mut handle_list: Vec<JoinHandle<()>> = vec![];

  for thread_id in 1..2u8 {
    let thread_fn = move || { 
      for i in 1..500_000_000 {
        println!("Hello {i} from thread {}", thread_id);
      }
    };
    let handle = std::thread::spawn(thread_fn);
    handle_list.push(handle);
  }

  handle_list[0].join(); // error appears here
}

Resulting error on .join() call:

cannot move out of index of Vec<JoinHandle<()>>
move occurs because value has type JoinHandle<()>, which does not implement the Copy trait

What Else I've Tried

  • Storing references to handles Vec<&JoinHandle<()>>
  • Using the Box<T> type to wrap references the join handles
  • Pre-allocating an array for JoinHandle<()> and wrapping in Option<T> (see below)

Attempt: Wrap Option<JoinHandle<()>> in Array

pub fn test_thread() {
  let mut handle_list: [Option<JoinHandle<()>>; 2] = [None, None];

  for thread_id in 0..=1usize {
    let thread_fn = move || { 
      for i in 1..500_000_000 {
        println!("Hello {i} from thread {}", thread_id);
      }
    };
    let handle = std::thread::spawn(thread_fn);
    handle_list[thread_id] = Some(handle);
    // handle_list.push(handle);
  }
  let handle_ptr = handle_list[0].as_ref().unwrap();
  (&handle_ptr).join(); // 🚨 Error: cannot move out of a shared reference move occurs because value has type `JoinHandle<()>`, which does not implement the `Copy` trait
}

:bulb: How do I go about solving this? Do I have to implement the Copy trait on Vec<JoinHandle<()>> or something? What other options do I have? I want a reference to each JoinHandle<()>, I am not trying to move them.

container[index] is actually syntactic sugar for *container.index(index)

so handle_list[0] moves out the ownership, and one solution is

-handle_list[0].join();
+handle_list.remove(0).join();

It's impossible due to the .join signature which takes the ownership JoinHandle.

3 Likes

Calling join() requires moving them. Since you want to wait for all the threads, do it like this, using a for loop that consumes the vector:

for handle in handle_list {
      handle.join();
}

If you wanted to join only one handle, you could remove() or swap_remove() it from the Vec.

But also, you could use std::thread::scope() instead, which automatically waits for every thread, instead of doing anything with the handles yourself.

6 Likes

Thank you, this worked perfectly.

Thanks for the alternate ideas @kpreid

You can also use swap_remove as mentioned above which is faster than remove.
The difference is swap keeps the order of the rest elements, but swap_remove doesn't.

And to know why x[i] moves out the ownership, you can refer to the Reference

2 Likes

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.