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
*workerwhich is behind a shared referencemove occurs because
*workerhas typemodels::worker::Worker, which
does not implement theCopytraitrustc(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.");
}
}
>Solution :
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 call run() more than once on the same ConsoleApp – then you can call 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 give you owned value that used to be in the reference, and leave a freshly default-created value as replacement. Since this will only copy the pointer and not the contents of the vector, and since an empty vec doesn’t allocate, this will be efficient as well as correct.
This approach will have a nice side effect of leaving the workers vector empty for subsequent invocations of run(). This is one of the situation where following the borrow checker results in simply – better code.