Can I return (?Send + ?Sync) from a JoinHandle?

I'm sorry for the lots of questions.

I'd like to know if it's possible to return a thread-local value from a thread:

pub fn compile(req: FineTunedCompileRequest) -> JoinHandle<CompilerResult> {
    // Spawn a thread for the compilation
    std::thread::spawn(|| {
        tokio::runtime::Runtime::new().unwrap().block_on(async move {
            let FineTunedCompileRequest {
                dir,
                workspace_dir,
                client,
                lockfile,
                lockfile_path,
            } = req;

            todo!()
        })
    })
}

CompilerResult here consists of a pair of Rcs which further contain other Rc and are totally thread-local. I'm getting an error complaning about that, but not as I expected (I thought join_handle.join().unwrap() could return anything).

There's no way to say "this type is Send under certain conditions" in safe code, and so Rc is always not Send. And join moves a value from one thread to another, which is exactly what Send allows. The Send bound isn't on join, but you can only create JoinHandle by spawn, which puts the bound on its closure.

Depending on what it does, you may be able to unsafe impl Send for CompilerResult {}, but a simpler solution is to just use Arc instead of Rc.

1 Like

I see, thanks. I decided to do compilation at the same thread I run some compiler processes, while the language server stays at the main thread.

Also it's difficulty to replace Rc by Arc in my project as it's very large and it also uses other thread-local types like RefCell (parser + semantic model + verifier...).

I discovered there is no way to kill threads immediately in Rust, so I think I'm satisfied with my route for now.

In case it matters (it likely doesn’t), note that Arc doesn’t require its contents to be Send + Sync — you can always substitute Arc<T> for Rc<T>, and if T isn't Send + Sync, then the only consequence is that Arc<T> isn’t Send + Sync. So, if the problem is only that you have generic code that uses Rc, you can swap in Arc. But this doesn’t help if the value you want to return from the thread contains a RefCell.

1 Like

FWIW, this is because it's incredibly dangerous to terminate threads on most operating systems.

There is a good overview of the problems you are likely to encounter on Linux in How to stop Linux threads cleanly if you do it anyway. MSDN hints that TerminateThread is difficult to use properly, and Raymond Chen asserts that there are no valid use cases for it because calling the function will corrupt your process.

The only safe way to cancel a thread is with its cooperation.

2 Likes