Skip to content

std::thread::JoinHandle should be marked with #[must_use] #48820

Closed
@ghost

Description

Here's a problem that caught my attention after reading @matklad's blog post titled Stopping a Rust worker. While the presented thread stopping mechanism is indeed very nice and convenient, I think a serious flaw is being overlooked: the spawned thread is never joined.

I've also noticed that spawning threads without ever joining has become a common pattern in Rust code. As a good example, take a peek at IndraDB's codebase.

Seeing something like the following should give everyone a pause and make them think twice:

thread::spawn(|| {
    // ...
});

Not only this thread is not joined, but the JoinHandle returned by thread::spawn is completely ignored. This is a pattern that should be discouraged.

So what's all the fuss about - why is not joining dangerous? Consider this:

use std::sync::mpsc;
use std::thread;

struct Foo;

impl Drop for Foo {
    fn drop(&mut self) {
        println!("Important cleanup work...");
    }
}

fn main() {
    let (s, r) = mpsc::channel::<()>();
    
    thread::spawn(move || {
        // Initialize some kind of resource.
        let _foo = Foo;
        
        // This loop ends when the channel becomes closed.
        for msg in r {
            // Process the message.
        }
    });
    
    // Close the channel.
    drop(s);
}

At the end of the main function, the channel is closed and the worker thread is signalled that no more messages will be sent. This automatically breaks the loop, which is nice.

However, the thread will not have enough time to actually take notice and drop all its variables. Instead, the thread is abruptly killed. That means it doesn't have a chance to do any kind of final cleanup work, like flushing buffers to disk, closing files, deallocating memory, or anything like that.

Even worse - the exit code is 0, Rust doesn't give any warnings, and everything looks good, while it really isn't!

I believe we should mark JoinHandle with #[must_use]. And if the user really doesn't want to join the thread (which should be very rare), they should acknowledge the possibility of the thread silently getting killed and write let _handle = thread::spawn(...) instead.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-enhancementCategory: An issue proposing an enhancement or a PR with one.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions