A simple problem of concurrent/parallel code

Sometimes I think I've understood something, but immediately after ALL my certainties collapse.

Consider the following example taken from "The Book" (Listing 16-3):

use std::thread;
fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(|| {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
    //println!("v = {:?}",v);
}

When compiling it, the indicated errors in the text are indeed obtained, but what I wonder is:

by invoking handle.join(), shouldn't it "block" the execution of the main thread until the logic of the secondary thread is completely executed? see join

In this way, the variable v wouldn't be deallocated, and the secondary thread could safely use the reference to v.

I try to rely on "Non-lexical lifetimes" and attempt the following explanation:
in the main thread, the variable v is no longer used after its declaration (or should I say after being captured by the closure?), and therefore it can be deallocated by the main thread, causing problems if this deallocation occurs before the borrowed value is used in the secondary thread.

But at this point, by uncommenting the println! statement after .join(), I think this println! should keep the variable alive, and the compiler shouldn't detect any potential hazardous situations (the secondary thread should use a borrow on a definitely "live" variable), but that's not the case.

Can someone understand where the error in my reasoning lies?

I hope this question hasn't been expressed in too convoluted a manner.

Thank for your time (and please have patience if I say colossal nonsense)

The main reason is due to the trait bound on spawn:

pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
    F: FnOnce() -> T + Send + 'static,
    T: Send + 'static,

The argument is required to be either a function or a closure that is Send and 'static. And the 'static bound requires the closure capture references with 'static lifetime or plain owned/non-referenced types.

By default, the closure prefers non-move capture modes. And to have a 'static capture and make your code compile, you'll need the move capture mode by using move keyword, since Vec<i32> is a non-referenced type.

In summary, make sure you're familiar with

thread::spawn doesn't know anything about join being used later. All it knows is its own signature.

1 Like

As the others noted, the fact that you join later doesn't effect the requirements on the API of the spawn function call. Note also that the thread you spawned could spawn it's own thread, pass it values, and then get joined while the third thread is still active. Example.

If you want the joining to be a requirement, that needs to be part of the API. See thread::scope.

3 Likes

Okay, thank you all.
Now I'll start digging deeper.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.