3

I'm new to Rust, so sorry if I am doing something trivially wrong.

I have a ConsoleApp class which holds a vector of Worker objects. I want each Worker to own a thread and handle everything related to it.

However, I have a problem at joining the thread that is hold by the Worker.

As you can see in the code below, I have a method named run having a mutable reference to self.

Spawning the workers (more specifically, the threads from the workers) works alright and it requires a mutable reference to the thread.

The issue is with the joining of the workers. When I call worker.join(), the following error is raised:

cannot move out of *worker which is behind a shared reference

move occurs because *worker has type models::worker::Worker, which does not implement the Copy traitrustc(E0507)

I think I do understand the problem: the join function from std::thread expects passing the JoinHandle by value, not by reference. Hence, I am supposed to make the join function from Worker to also expect passing self by value. However, how can I do that given that run has mut &self?

ConsoleApp:

pub struct ConsoleApp { accounts: Vec<Account>, workers: Vec<Worker>, } impl ConsoleApp { pub fn new() -> ConsoleApp { return ConsoleApp { accounts: initialize_accounts(), workers: Vec::new() } } pub fn run(&mut self) { println!("Start console app."); self.workers.iter_mut().for_each(|worker| worker.spawn()); self.workers.iter().for_each(|worker| worker.join()); println!("Stop console app.") } } 

Worker:

use std::thread; pub struct Worker { thread_join_handle: thread::JoinHandle<()> } impl Worker { pub fn spawn(&mut self) { self.thread_join_handle = thread::spawn(move || { println!("Spawn worker"); }); } pub fn join(self) { self.thread_join_handle.join().expect("Couldn't join the associated threads."); } } 

2 Answers 2

3

The obvious way would be to make run() consume self as well. Then you could write it like this:

pub fn run(mut self) { println!("Start console app."); self.workers.iter_mut().for_each(|worker| worker.spawn()); self.workers.into_iter().for_each(|worker| worker.join()); println!("Stop console app.") } 

Note the use of into_iter() which iterates over workers while taking over the contents of the vector. Of course, self.workers.into_iter() would not compile in a method that takes &mut self.

If modifying run() to consume self is not possible - for example, because you'd like to allow run() being invoked more than once on the same ConsoleApp - then you can use std::mem::take() to extract the workers from &mut self:

pub fn run(&mut self) { println!("Start console app."); self.workers.iter_mut().for_each(|worker| worker.spawn()); std::mem::take(&mut self.workers) .into_iter() .for_each(|worker| worker.join()); println!("Stop console app.") } 

std::mem::take(&mut self.workers) is a convenient short-hand for std::mem::replace(&mut self.workers, Vec::default()). It will move the value out of the reference and leave a freshly default-created value as its replacement. Since this will only move the pointer and not the contents of the vector, and since an empty vector doesn't allocate, the operation will be efficient as well as correct.

This approach will have a nice side effect of leaving self.workers empty for subsequent invocations of run(). This is one of the situation where following the borrow checker results in simply - better code.

Sign up to request clarification or add additional context in comments.

2 Comments

You are right, I also tried that, but my problem was that self.workers.iter().for_each(|worker| worker.join()) would still fail. But indeed, I should use into_iter instead and consequently, that problem also disappears. Thank you! 😊
@AlexandruRobert Sure, but in the question you specified run taking &mut self as a constraint. I'm pretty sure self.workers.into_iter() doesn't compile if run() takes &mut self...
0

Another solution is to put thread_join_handle inside an Option<thread::JoinHandle<()>>, and only join if it is not None.

With this approach the Worker.join can take &mut self, and a worker stays for your disposition after finishing (e.g. if you want to examine the results, or restart it in ConsoleApp).

Approaches by @user4815162342 are good as well.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.