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| });
11|
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.
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():
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}"));
})
.unwrap();
}
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>>
where
F: FnOnce(&Scope<'env>) -> R;
impl<'env> Scope<'env> {
pub fn spawn<F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T>
where
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.