Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Rust: How to call a method with self from a method with &mut self

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.");
  }
}

>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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading