Why is this Rust code for waiting for a thread to finish not working?

Viewed 443

I have some multi-threaded code that's giving me trouble. This is as simple as I can reproduce it:

use std::thread;
use std::time;
use std::sync::{Arc, atomic::{Ordering, AtomicBool}};
use std::ops::Drop;

struct Container {
    // Join Handle for a thread
    th: Option<thread::JoinHandle<()>>,

    // Gets set to true when we want the thread to exit
    stop_thread: Arc<AtomicBool>,
}

impl Container {
    fn new() -> Self {
        // Create new instance
        let mut inst = Self {
            th: None,
            stop_thread: Arc::new(AtomicBool::new(false)),
        };

        let stop_thread = inst.stop_thread.clone();

        // Start a new thread that does some work
        let t = thread::spawn(move || {
            // Keep doing work until stop_thread gets set to true
            while !stop_thread.load(Ordering::SeqCst) {
                println!("Doing stuff...");

                thread::sleep(time::Duration::from_secs(1));
            }

            println!("Thread exited");
        });

        inst.th = Some(t);
        inst
    }
}

impl Drop for Container {
    fn drop(&mut self) {
        self.stop_thread.store(true, Ordering::SeqCst);

        if let Some(t) = self.th {
            t.join().unwrap();
        }
    }
}

fn main() {
    let c = Container::new();

    thread::sleep(time::Duration::from_secs(3));
    drop(c);
}

The idea is that when a new instance of the Container struct is created, a background thread is started that does something. It keeps running until the instance is destroyed, at which point, I need the thread to be notified that it needs to exit. I also need to actually wait for the thread to exit before proceeding.

Everything works great, except for the code in the drop function. Rust is unhappy with if let Some(t) = self.th. It says:

error[E0507]: cannot move out of `self.th.0` which is behind a mutable reference
  --> src/main.rs:45:26
   |
45 |         if let Some(t) = self.th {
   |                     -    ^^^^^^^ help: consider borrowing here: `&self.th`
   |                     |
   |                     data moved here
   |                     move occurs because `t` has type `JoinHandle<()>`, which does not implement the `Copy` trait

Why can't I do this? What is self.th.0?

When I try to take Rust's suggestion, and do if let Some(t) = &self.th instead, it still doesn't compile:

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

What am I doing wrong?

1 Answers

As specified in this answer (linked by Rabbid76), this can be worked around by using the .take() function:

impl Drop for Container {
    fn drop(&mut self) {
        self.stop_thread.store(true, Ordering::SeqCst);
        if let Some(t) = self.th.take() {
            t.join().unwrap();
        }
    }
}

Though you might wanna consider if waiting on another thread inside the drop implementation is a good idea as explained here

Related