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)
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 self
instead 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)