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

Kea*_*uan 3 rust

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

Sve*_*rev 6

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>
Run Code Online (Sandbox Code Playgroud)

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<()>>,
}

Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

  • @Keanyuan 另一种选择是让“Executor”上的“join()”方法也按值接受“self”,这会导致“self.t.join().unwrap()”[编译](https:// /play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2018&amp;gist=2da9205c57f87f84ded84515ccc19068)。它还有一个额外的好处,即允许(在编译时)“Executor::join()”仅被调用一次,并使“Executor”对象在调用之后不可用(这是有道理的,因为它刚刚关闭了它的工作线程)。 (3认同)