A problem I am facing with join method

A problem I am facing with join() method

Reading the rust book I have stumbled upon this. In the book it's said that join() blocks the main thread until associated thread finishes it work.

Now, kindly look at this code:

1 |  use std::thread;
2 |
3 |  fn main() {
4 |      let s = String::from("hi");
5 |
6 |      let r1 = &s;
7 |
8 |    	 let handle = thread::spawn(move || {
9 |          println!("{}", r1);
10|   	 });
12|   	 handle.join().unwrap();
13|  }

In theory, as I am doing handle.join() , s should be dropped after the spawned thread finishes its work. But the error says:

let mut s: String
Go to String

`s` does not live long enough
borrowed value does not live long enough (rustcE0597)
main.rs(13, 1): `s` dropped here while still borrowed
main.rs(8, 18): argument requires that `s` is borrowed for `'static`

So the error is stating that s is dropped at the end of scope while I am trying to use it in another thread. Am I understanding join() wrong?

That happens because of the signature of std::thread::spawn - it takes a closure with 'static lifetime. So it doesn't matter how or when you join.
If you want scoped threads, you should use the one from crossbeam.

1 Like

The problem is, even though join() is called before main returns, the borrow checker is not allowed to use that information. Consider the signature of std::thread::spawn():

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

The F: 'static bound means that the closure must own its own data; it cannot borrow anything from the surrounding function. What you're looking for is scoped threads. One popular implementation is that of crossbeam-utils:

use crossbeam_utils::thread;

fn main() {
    let s = String::from("hi");
    let r1 = &s;
    thread::scope(|s| {
        s.spawn(|_| println!("{r1}"));

Thanks so much for the detailed answer! Can I ask why the the borrow is not allowed to use this information that join is called before main exits?

The borrow checker cares only about function signatures when determining lifetime requirements. In this case, since spawn() requires the closure to be 'static, the borrow checker ensures that the closure contains no borrowed data. It can perform no analysis on the resulting JoinHandle, since the type simply doesn't have the relevant lifetime information.

In contrast, look at the function signatures for scoped threads:

pub fn scope<'env, F, R>(
    f: F
) -> Result<R, Box<dyn Any + 'static + Send, Global>>
    F: FnOnce(&Scope<'env>) -> R;

impl<'env> Scope<'env> {
    pub fn spawn<F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T>
        T: Send + 'env,
        F: FnOnce(&Scope<'env>) -> T + Send + 'env;

Here, the Scope::spawn() method returns a ScopedJoinHandle with a lifetime. Since this lifetime is bounded above by the &Scope<'env> reference, the borrow checker knows for certain that all threads will be joined by the time the scope() call ends.

1 Like

you are the best. Thanks again for the great explanation :grinning:

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.