Non-Detachable Threads

Coming from a C++ background, something that always seemed odd to me about rust was the default behavior of rust's threads being detachment rather than a join. Despite rust obviously enforcing much more safety standards than C++, it allows implicit detachment of threads. For example, this code in C++:

#include <thread>
#include <cstdio>

int main() {
    auto t1 = std::thread([]{
         int a = 3;
         for(;a < 5;++a) {
              std::puts("Hello World!");
         }
    });
    return 0;
}

Results in a crash because the thread is not joined nor detached. But the same code on rust is perfectly valid

use std::thread;
  
fn main() {
    let t1 = thread::spawn(|| {
         println!("Hello!");
    });
}

The reason I bring this up is:

  1. If desired, there's no way to enforce threads have been joined before the program exits, as in, no equivalent of lifetimes for threads.
  2. A thread should be able to borrow a non static reference if it is guaranteed to be joined before the value behind the reference goes out of scope.

For 2, consider that rust already does not allow moves if a borrow exists in the current scope, such as

fn main() {
   let a = String::from("Hello World!");
   let b = &a;
   drop(a);
   let c = *b;
}

results in

error[E0505]: cannot move out of `a` because it is borrowed
  --> src/main.rs:30:9
   |
29 |    let b = &a;
   |            -- borrow of `a` occurs here
30 |    drop(a);
   |         ^ move out of `a` occurs here
31 |    let c = *b;
   |            -- borrow later used here

So the idea of a non-detachable thread would be a new type of thread such that a thread would either be joined on drop, or must be joined before the end of it's scope it was spawned in.

fn foobar() {
   let a = String::from("Hello World!");
   let t1 = thread_non_detachable::spawn(|| {
       println!("Using a message {:?}", a);
   })
   
   // t1 does not live beyond this scope, it either is joined,
   // or joined on drop
}

Rayon provides this construct: scope in rayon - Rust.

Destructors are not sufficient to enforce safety here because objects can be safely leaked.

11 Likes

Can you clarify what you mean by leaked ? Are you referring to the references the thread would hold ? I gave an example above how currently today, you can't move an object out of scope that is currently being borrowed.

EDIT: I see, so this would require an additional constraint in that if a reference is used across another thread for a local variable, the value behind that reference cannot be moved.

Also, the rayon scope is cool, but those are tasks rather than threads, that run on a thread pool rayon has. This concept is for actual threads.

See https://github.com/rust-lang/rust/issues/24292 and https://www.reddit.com/r/rust/comments/57c5uq/what_happened_to_threadscoped/.

2 Likes

interesting, so it seems the RAII guard isn't enough to secure the thread handle

See https://github.com/rust-lang/rfcs/pull/3151

5 Likes

crossbeam offers the same scope API for full threads.

9 Likes