Why can’t I use a thread handle which is stored in a struct?

Viewed 162

I want to implement an executor. I want to store the thread handle in a struct, so I can join it later, waiting for the threads to stop gracefully.

But I get an error when I try to call the join() method. I would like to know the reason and how to fix that.

struct Executor{
    tx:Sender<Box<dyn Send + Fn()>>,
    t:JoinHandle<()>,
}
impl Executor {
    fn new()->Self{
        let (tx, rx) = mpsc::channel::<Box<dyn Send + Fn()>>();
        let handle = thread::spawn(move || {
            loop{
                //TODO: check stop flag
                rx.recv().unwrap()();
            }
        });
       
        let ins = Executor{tx:tx, t:handle};
        ins
    }
    fn sender(&self)->Sender<Box<dyn Send + Fn()>>{
        self.tx.clone()
    }
    fn join(&self){
        self.t.join().unwrap();
    }
}

The error:

error[E0507]: cannot move out of `self.t` which is behind a shared reference
  --> src\main.rs:77:9
   |
77 |         self.t.join().unwrap();
   |         ^^^^^^ move occurs because `self.t` has type `JoinHandle<()>`, which does not implement the `Copy` trait
1 Answers

You cannot use it like that, because the join() method takes ownership of the handle, thus moving it out of the struct.

pub fn join(self) -> Result<T>

But if you take it out of the struct, then that memory location would contain invalid data and your struct would be in invalid state. Imagine you call join() once, the handle is moved out of the struct, then you try to call join() for a second time - the second call would access uninitialized memory, which is UB.

So you have to handle the case when there is no JoinHandle present. This can be done by using Option<JoinHandle>:

struct Executor{
    tx:Sender<Box<dyn Send + Fn()>>,
    t: Option<JoinHandle<()>>,
}

Then your join method can safely take out the handle from the struct:

fn join(&mut self){
    if let Some(handle) = self.t.take(){
        handle.join().unwrap();
    }
}

As @user4815162342 has noted in the comments, in that particular case it might be better to make your join() accept selfinstead of using Option<JoinHandle<>>in order to prevent someone from using the executor after it has been stopped:

fn join(self){
    self.t.join().unwrap();
}
Related