Description
There are a few places where the semantics we enforce for closures could not be faithfully simulated by end-users. These omissions are accidental, but programs do rely on them, and removing them breaks reasonable looking programs. I identified these as part of a patch that exposed closures in a more accurate way.
The model that PR #27087 puts forward is that closures can be modeled as a structure like:
struct Closure<'l0...'li, T0...Tj, U0...Uk> {
upvar0: U0,
...
upvark: Uk
}
where 'l0...'li and T0...Tj are the lifetime and type parameters in scope on the function that defined the closure, and U0...Uk are type parameters representing the types of its upvars (borrowed, if appropriate). I'll not go into all the details here of why I chose this structure (it's explained in a comment in the code), but I do want to point out that clearly 'l0...'l1
and T0...Tj
do not appear in the fields -- they are there so that when we are monormorphizing, we can easily access the "in-scope" substitutions from the enclosing fn, which may be needed for method calls and so forth.
Now, because 'li
and Ti
are unused, if a user were to write this, it would require a PhantomData
. This would in turn make OIBIT consider those type parameters to represent reachable data, which might affect Send
and so forth. However, the existing code only considered the types of a closure's upvars when computing OIBIT calculations, and I have preserved those semantics. This has a concrete impact if you have code like:
fn foo<T:Default>() {
send(|| { let x = T::default(); ... })
}
Here, the closure is required to be sendable. This is considered true because it doesn't close over any state. However, if this were a struct with phantom data, it would be considered false, because the struct would be considered to potentially reach data of type T
, which is not Send
.
There is a similar case in the outlives relation:
impl<'a> Foo<'a> {
fn method(&self) {
let x = || something_that_does_not_use_self();
bar(x); // requires that x: 'static
}
}
fn bar<T: 'static>(t: T) { }
Here, we have an early-bound region 'a
in-scope, and hence the type of x
would correspond to C<'a>
(there are no upvars and no type parameters). However, bar
requires that C<'a>: 'static
. This is currently considered to hold because there are no upvars. However, for a normal struct, this would require that 'a: 'static
, which is false.
This last point means that this closure can escape 'a
, since there is no actual reachable data with that lifetime. (This is also true for regular fns and I think true for impls since they are permitted to have extra lifetime parameters.)
Anyway, we need to consider whether and how to close these gaps -- perhaps by extending normal structs, perhaps by limiting closures.
cc @rust-lang/lang