correct, assuming you mean the closure passed to thread::scope
. Note that there's a difference between the whole thread::scope
call, and the |s| { ... }
closure you pass to it. That closure is invoked some time during the whole thread:scope
call.
correct, assuming you mean the closure passed to s.spawn
. A thread is not a closure; this closure of course is the main action of what the thread will be executing, but still, a thread is a thread, not a closure 
There are 3 things about the scope. The whole thread::scope
call, the "|s| { ... }
"-closure passed to it, and that s: &'scope Scope<'scope, 'env>
object. The s: &Scope<…>
object itself doesn't really "do" anything, and has 2 different lifetime arguments to it, as you can see, and while the precise meanings of 'scope
and 'env
are an interesting (and complex) topic on its own, they don't really matter so much. The reasoning you mention speaks of responsibility to complete the spawned thread, and that's something that the thread::scope
function does. Calling s
the “scope” is a natural simplification, but really, this whole construct is the “scope”.
The whole thread::scope
call certainly does last longer than all individually spawned threads. This isn't something the compiler can know anyways, it only knows whatever type signatures are telling it. The “current function”, i.e. the closure passed to thread::scope
however, does absolutely not live as long.
This is indeed roughly what's happening; just it's not the thread::scope
call or s
, but the _closure passed to thread::scope(..)
we are talking about. That returns first, before the surrounding thread::scope
call, which isn't done yet then, goes and starts waiting for all the threads to finish; and that's natural, it's just an ordinary closure, how could it not return immediately, first thing, as soon as it's finished kicking off the thread with s.spawn
, it's last statement?
As mentioned, the compiler message is all about type signatures, because that's the way the compiler understands any of this.
The compiler does indeed determine that the || { ... println! ... }
-closure passed to s.spawn
must outlive the |s| { ... s.spawn(..) ...
-closure. Why? Type signatures! We have
pub fn scope<'env, F, T>(f: F) -> T
where
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T,
where there's a livetime 'scope
introduced; the full compiler message you get calls this lifetime “'1
”.
FULL COMPILER MESSAGE
error[E0373]: closure may outlive the current function, but it borrows `numbers`, which is owned by the current function
--> src/main.rs:7:18
|
4 | thread::scope(|s|
| - has type `&'1 Scope<'1, '_>`
...
7 | s.spawn( ||
| ^^ may outlive borrowed value `numbers`
8 | {
9 | println!("{:?}", numbers);
| ------- `numbers` is borrowed here
|
note: function requires argument type to outlive `'1`
--> src/main.rs:7:9
|
7 | / s.spawn( ||
8 | | {
9 | | println!("{:?}", numbers);
10 | | });
| |__________^
help: to force the closure to take ownership of `numbers` (and any other referenced variables), use the `move` keyword
|
7 | s.spawn( move ||
| ++++
For more information about this error, try `rustc --explain E0373`.
This signature establishes that 'scope
lifetime appears as a lifetime parameter in an argument in the signature of F
. The higher-order nature of this lifetime isn't really relevant here by the way; really, the fundamental principle of lifetimes that "(external) lifetime parameters in function signatures outlive the function call" is all that matters. The lifetime 'scope
will outlive the function call to f: F
, which is the |s| { ... s.spawn(..) ...
-closure.
The other puzzle-piece then is the Scope::spawn
signature:
impl<'scope, 'env> Scope<'scope, 'env>
pub fn spawn<F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T>
where
F: FnOnce() -> T + Send + 'scope,
T: Send + 'scope,
Here, the important thing of note is F: 'scope
. This says the spawned || { ... println! ... }
-closure must outlive 'scope
. The compiler calls this out as “function requires argument type to outlive `'1`
”.
This completes the compiler's reasoning why (from the compiler's POV) the || { ... println! ... }
-closure must outlive the |s| { ... s.spawn(..) ...
-closure. The former must outlive 'scope
and 'scope
must outlive the latter.