In this example, the compiler reports an error at #1:
borrowed data escapes outside of closure s escapes the closure body here
What's the reason here, if the lifetime ''a' introduced in the higher-ranked bound is changed to declare in the signature of scope_spawn, something like this
fn scope_spawn<'a, 'b:'a,F>(mut f:F)
where F: FnOnce(&'a Scope<'a,'b>){
}
That error will be eliminated. What's the difference here? IIUC, the compiler error may be caused by that for<'a> FnOnce(&'a Scope<'a,'b>) imposes that 'b:'a, so borrowing the &'a Scope<'a,'b> for 'b will exceed the lifetime of &'a Scope<'a,'b>, however, why the changed one does not have an error caused by the same reason?
HRTB requires the FnOnce impl is valid for all 'a. The latter one only requires it's valid for one specific 'a (for example, the compiler is free to choose 'a == 'b).
struct Scope<'a,/* 'b */>(PhantomData<* mut &'a ()>,/* PhantomData<* mut &'b ()>*/);
impl<'a,/*'b*/> Scope<'a,/*'b*/>{
fn take<T:'a>(&'a self, t:T) where T:FnMut(){}
}
fn test<'d,F>(f:F)
where F: for<'a> FnOnce(&'a Scope<'a>){
}
fn main(){
let i = 0;
test(|s|{
s.take(||{
let r = i; // #1
})
});
}
Without the second lifetime in the comment, #1 has an error argument requires that i is borrowed for 'static. I wonder whether the &'a Scope<'a,'d> implicit that 'd:'a, and in this situation does 'a denote any lifetime anymore? Is where F: for<'a> FnOnce(&'a Scope<'a,'d>) a higher-rank trait bound anymore?
No, there is no implicit 'd: 'a. For reference, Scope has the 'd: 'a (aka 'env: 'scope) bound in its declaration.
Sure. It just happens to constrain'd if we have the 'd: 'a bound in Scope or in the function signature of test:
To get a for<'scope where 'env : 'scope> quantification in current Rust, we carry an implicit 'env : 'scope bound inside the Scope type itself, so that for<'scope> quantifications that "see" the Scope<'scope, 'env> type end up correctly constrained. Hence why Scope also needs to be infected with 'env2.
IIUC, the difference between 'd: 'a and for <'a where 'd:'a> (which is not real syntax but used to describe the relationship in the tracking issue of scoped threads) is that 'd can never be equal to 'a, because 'a is not a lifetime that the caller can specify. Hence we can't leak a Scope or a ScopedJoinHandle outside of the closure we pass to test (aka thread::scope).
However, even though there is no trait bound like 'd:'a in the declaration of Scope, it still work, if comment the second lifetime for Scope, the compiler says i should be borrowed for 'static, what's the reason here?
spawn requires the reference lifetime be the same as the second lifetime parameter; the lifetimes are invariant so you can't shrink and you would need to create a &'b Scope<'_, 'b> specifically; you only have a reference lifetime equal to the first lifetime parameter, so you get a lifetime error.
It does denote any lifetime. For example, it could denote 'static. The error message mentioning 'static is just an example of a lifetime that could be problematic. It doesn't mean 'a can only be 'static or that 'static is the only problematic lifetime.
&'a Scope<'a, 'd> in a signature does imply 'd: 'a. (But perhaps making this a property of std's Scope itself is needed elsewhere. Or maybe they're just being prudent.)
I don't see why 'd couldn't be equal. Edit: Ok, after refreshing my memory, maybe you meant in the stdScope implementation specifically; I think that's true there (but it's not a conclusion you can make from the bound).
But at least one purpose of the higher-ranked bound in this case and cases like GhostCell is that the closure body has to work for all lifetimes 'a, which mean the closure body can't "know" what it is; you can't assume that it's equal (or not equal) for example.
The where 'd: 'a in the case of Scope is to put an upper limit on the unknown lifetime 'a.
Yes, I think I misinterpreted the following quote (my emphasis) from this comment when I said 'd can never be equal to 'a:
"some lifetime that is internal to 'scope but longer than the call to this closure"
I wrongfully equalled 'scope to specify the lifetime of the closure call, but it is slightly longer than that:
The 'scope lifetime represents the lifetime of the scope itself. That is: the time during which new scoped threads may be spawned, and also the time during which they might still be running. Once this lifetime ends, all scoped threads are joined. This lifetime starts within the scope function, before f (the argument to scope) starts.
So 'env can be equal to 'scope, but 'scope (thanks to the invariance of it in Scope) will always be longer than the lifetime of the closure call.
Why is this example ok? At #1, the type of s is &'a Scope<'a,'b>, and s.spwan(...) can be simplified to let tmp:& 'b Scope<'a,'b> = s, the lifetime 'a and 'b is invariant on Scope, the implicit requirement in the signature of scope_spawn is 'b:'a(inferred from type &'a Scope<'a,'b>) and that requirement is also the explicit one, we didn't impose 'a:'b such that 'a == 'b, why is this example ok?
You've taken away the requirement that the caller of scope_spawn work with every lifetime 'a shorter than 'b, and given the caller the ability to choose 'a (and still choose 'b). The caller can just choose any lifetime 'x so that the out closure accepts &'x Scope<'x, 'x> for example, which satisfies the signature of spawn.
The key part is that you gave away the choice of 'a to the caller. That took away the imposition that the closure works with some unknown lifetime (within the constraints). With the higher-ranked bound, the closure has to work with lifetimes shorter than 'b. When the caller can choose a single lifetime, it doesn't have to.
Hmmm, that is, the caller selects a lifetime for 'a that is the same as 'b, and the selected one forms a function whose parameter type is &'b Scope<'b,'b> that also satisfy the requirement in fn scope_spawn<'a, 'b:'a,F>(mut f:F) where F: FnOnce(&'a Scope<'a,'b>){ } because 'b:'b.
In contrast to the first example where the compiler reports an error. Since 'a and 'b are invariant on Scope, if s.spawn were to success, the compiler would select 'a as 'b, which would implement the closure not general enough, so the compiler can only select one from two options, it selects one that the implementation is general enough
In this example, the lifetime 'a and 'b are covariant on Scope, the signature of scope_spawn implicitly has a requirement 'b:'a(i.e. for<'a where 'b:'a>), the &'a Scope<'a,'b> can be coerced to &'b Scope<'a,'b> because it is regulated to by another subtype relation, right?
Since non-higher-ranked lifetime 'b substitutes higher-ranked lifetime 'a, hence &'a T is a subtype of &'b T, the coercion is permitted here, so the example can be compiled, right?
&'a _ can't expand to &'b _, but it can coerce down to &'a Scope<'a, 'a> or some smaller &'b0 Scope<'a0, 'b0> that meets the bound.
There's a higher-ranked trait bound here, but no higher-ranked types. (for<..> as part of the type (which might be elided for fn pointers and dyn Fn types)).
scope_spawn(|s:/* &'a Scope<'a,'b> */|{
// let borrow:&'b Scope<'b,'b> = s; // #1
/* s => borrow */.spawn(||{
});
});
As shown in #1, we borrow a reference from s to call spawn, is 'a representing higher-ranked lifetimes in this context? It appears to me that the lifetime 'a is like something as if it were introduced like
for<'a where 'b:'a> | s:/* &'a Scope<'a,'b> */ |{
// let borrow:&'b Scope<'b,'b> = s; // #1
// borrow.spawn(||{});
}
'b:'a expresses that 'b outlives 'a? If 'a is a higher-ranked lifetime, &'a T should also be the subtype of & 'b T, right? So coercing &'a T to &'b T is ok and vice versa, right?
You're annotation on s: is correct, with the additional information that it has to work for every 'a[1] but only one 'b.
But #1 can't be a &'b Scope<'b, 'b> because 'a may be shorter than 'b and covariant lifetimes cannot grow longer, only shorter. However, it could be &'a Scope<'a, 'a>,[2] because 'b is also covariant in this example.
Yes.
&'a T isn't a higher-ranked type. In order to call the closure, one specific lifetime 'a has to be chosen first. The body in this example has to work for any 'a,[3] but 'a is a single lifetime within the body. There are no higher-ranked types in this example, so the sub/supertype relationships around higher-ranked types is a red herring (an irrelevant distraction).
Rust has no for<'a> &'a T types -- no higher-ranked reference types. Function pointers and dyn Trait are the only higher-ranked types.
In covariant position, longer lifetimes are subtypes of shorter lifetimes (which can be confusing). You can coerce a subtype to a supertype but not vice-versa.[4] So you can coerce &'b T to &'a T but not vice-versa.