How do i spawn a thread and join it in destructor ? This seems natural to me but is prevented because JoinHandler's join moves the value out. How do I achieve this idiom ?
struct A {
flag: Arc<Mutex<bool>>,
join_handle: thread::JoinHandle<()>,
};
impl A {
pub fn new() -> A {
let flag = Arc::new(Mutex::new(false));
let flag_clone = flag.clone();
A {
flag: flag,
join_handle: thread::spawn(move || {
while !*flag_clone.lock().unwrap() { /* Do Something */ }
}),
}
}
}
impl Drop for A {
fn drop(&mut self) {
*self.flag.lock().unwrap() = true;
self.join_handle.join(); // Error: cannot move out of type `A`, which defines the `Drop` trait
}
}
hahaha .. Brilliant !! Need much more practice with Rust to actually start thinking those myself. Do post these things somewhere and let us all know if there are more nifty/interesting tricks like theses up your sleeve
It's still the recommended way for cases like this. A Box doesn't help here because you can't move out of it inside drop() for the same reason you cannot call JoinHandle::join() in there - self is borrowed, and you cannot move out of it.
To be fair, that's unlikely to be a concern in this particular case .
BTW, I'm not sure if @blakehawkins's question is general or specifically about joining a thread in drop(), but if it's the latter, it's worth mentioning that drop() isn't guaranteed to run (e.g. someone can std::mem::forget() the value) and so relying on it for joining a thread is brittle.
To be fair, that’s unlikely to be a concern in this particular case .
Fair enough
Even though std::mem::forget is not marked as unsafe, I'm wondering whether it should be precisely because it allows you to skirt the regular ownership rules, similarly to unsafe itself.
My point is that once you use that kind of thing somewhere, all guarantees regarding memory safety are up to the programmer to provide rather than the compiler, and I see whether that is thread-related or not as a more shallow issue.
Leaking memory (and thus foregoing Drop) is safe, on its own. In this particular case, there won't be a memory safety issue - the caller simply won't know the result of the thread's computation, nor whether it finished (since it's running detached). But that's not a "safety" issue in terms of how Rust defines the word.
Back in the day, when JoinHandle itself had a Drop that was relied upon to allow the thread closure to borrow from the environment, now that was a safety issue because the thread can end up accessing invalid memory.
forget was changed from unsafe to safe in large part because it's not feasible to prevent safe Rust code from leaking values. Even without forget you can write safe Rust code that prevents destructors from running, for example by creating a cycle of Rc pointers. There's no way for forget to cause undefined behavior in safe Rust code as long as unsafe code doesn't rely on destructors running. The Rustonomicon has more discussion of this.