I try to grasp the trick that controls the borrowing lifetime for a specific variable, like what the standard std::thread::scope does. I simplified the utility to the following:
use std::marker::PhantomData;
struct Scope<'env>{
_phantom:PhantomData<* mut &'env ()>,
}
impl<'env> Scope<'env>{
fn spawn<F>(self:& Scope<'env>,_f:F) where F:FnMut()+'env{
}
}
fn start<'env,F>(mut f:F)
where F:FnOnce(& Scope<'env>)+'env{ // #1
let scope:Scope<'env> = Scope{_phantom: PhantomData::default()};
f(&scope);
}
fn main() {
let mut i = 0;
start(|s|{
s.spawn(||{
let rf = & mut i; // #2
});
// let rf = & mut i; // #3
});
}
It does work. If you uncomment #3, then you will get the expected error:
error[E0499]: cannot borrow `i` as mutable more than once at a time
--> src\main.rs:22:16
|
18 | start(|s|{
| - has type `&Scope<'1>`
19 | s.spawn(||{
| - -- first mutable borrow occurs here
| _______|
| |
20 | | let rf = & mut i;
| | - first borrow occurs due to use of `i` in closure
21 | | });
| |________- argument requires that `i` is borrowed for `'1`
22 | let rf = & mut i;
| ^^^^^^^ second mutable borrow occurs here
However, if I change FnOnce at #1 to FnMut, then #1 directly causes an obscure error without commenting #3. The error is:
error[E0521]: borrowed data escapes outside of closure
--> src\main.rs:19:7
|
18 | start(|s|{
| - `s` declared here, outside of the closure body
19 | / s.spawn(||{
20 | | let rf = & mut i;
21 | | });
| |________^
|
= note: requirement occurs because of the type `Scope<'_>`, which makes the generic argument `'_` invariant
= note: the struct `Scope<'env>` is invariant over the parameter `'env`
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
I don't know the reason here. Where does the constraint that requires s to be alive outside the first closure body? IIUC, the receiver type of spawn is & Scope<'env>, where the implicit lifetime is unconstrained and is covariant. Moreover, the type wouldn't require the implicit lifetime outlives 'env.
Why does changing FnOnce to FnMut cause the error? How to understand it?
The outer closure has to capture a &mut i with a lifetime of at least 'env (of the start caller's choosing). Then the inner closure has to reborrow through that capture for at least 'env.
That's fine if the outer closure is a FnOnce -- you can just reborrow for the entire lifetime of the outer capture, which is like moving the &mut i into the inner closure. (FnOnce takes the closure by value.)
But if the outer closure can be called more than once via FnMut, you only have access to a &'this_call mut &'env captured_i (FnMut takes a &mut to the closure) and you can't reborrow for 'env through that. (You can't borrow any owned captures for 'env through that either, so move doesn't help.)
Sorry for the lack of examples (I'm on mobile). In the meanwhile you could take this notional desugaring and implement the closures and their Fn trait implementations manually on nightly to see the error more clearly.
(I didn't evaluate your scope API beyond the errors under discussion.)
the inner closure must reborrow i from self of the outer closure, which looks like &'r mut *(*self).captured, the Supporting prefixes are: *(*self).captured, (*self).captured, *self, self. So, the corresponding reborrowing constraints are: 'env:'r, 'this_call:'r, and 'r:'env due to F:'env imposed by the constraint in spawn, so this causes that 'this_call:'env? The parameter mut f cannot be borrowed for the lifetime that is at least as long as 'env, which is the reason for the error, right?
However, why does the error imply that "s declared here, outside of the closure body"? The actual error seems to be irrelevant to s but to f.
The error diagnostic are doing their best to put some borrow check error due to a constraint violation into English. Sometimes it's good, sometimes it's not. This particular diagnostic looks half-complete to me: it points out s but not where the "escaping" occurs.
If s: &Scope<'env> could be coerced to s: &Scope<'this>, so that you could call Scope::<'this>::spawn instead of Scope::<'env>::spawn, the closure could meet the lifetime bound on Scope::<'_>::spawn.[1] But due to invariance, that coercion can't happen. That's probably why it's highlighting s. But it fails to highlight the bound requiring something to be equal to 'env.
The report error in your manual implementation is close to what I understand from your answer above. Cite the reasonable error here
error: lifetime may not live long enough
--> src/main.rs:62:17
|
60 | impl<'a: 'env, 'env> FnMut<(&Scope<'env>,)> for ClosureInner<'a> {
| ---- lifetime `'env` defined here
61 | extern "rust-call" fn call_mut(&mut self, args: (&Scope<'env>,)) -> <Self as FnOnce<()>>::Output {
| - let's call the lifetime of this reference `'1`
62 | let s = args.0;
| ^^^^^^ assignment requires that `'1` must outlive `'env`
|
= note: requirement occurs because of the type `Scope<'_>`, which makes the generic argument `'_` invariant
= note: the struct `Scope<'env>` is invariant over the parameter `'env`
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
Presumably, the ClosureInner here should denote ClosureOuter. The compiler complains that we cannot borrow the outer_closure for lifetime 'env such that 'l:'env, where outer_closure corresponds to f in my original question.
However, the error says "s outside of the closure body" is a bit strange and misleading, which could cause people to think that the escaped data refers to s.