-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Semantics of closures and structs do not exactly correspond #27086
Comments
Isn't this only important from a function-local perspective? Because of the lack of HKT, extra-function closures are present only when they are substituted into other types - and in that case, the exact value of the substitutions doesn't matter (in fact, AFAIK closure signatures can even contain Actually, that's not exactly correct - as you said, closures can essentially use use std::marker::PhantomData;
pub struct InvariantType<T>(PhantomData<*mut T>);
impl<T> Copy for InvariantType<T> {}
impl<T> Clone for InvariantType<T> { fn clone(&self) -> Self { *self } }
unsafe impl<T> Send for InvariantType<T> {}
unsafe impl<T> Sync for InvariantType<T> {} It may be wise to introduce |
I can't remember who pointed this out, but it's currently possible to use PhantomData with a function pointer type: use std::marker::PhantomData;
pub struct Invariant<T>(PhantomData<fn(&T)>);
fn send<T:Send>(_:T) {}
fn foo<T:Default>() {
send(Invariant::<T>(PhantomData));
}
fn main() {
foo::<i32>();
} rustc correctly figures out that Invariant is Send. |
That's contravariant. That's just a nit, anyway. |
You can in fact semantic-equivalently desugar closures using fn-items (with ABI differences, mostly involving fn-items not being zero-sized): struct MutClosure<B, E> {
body: B,
environment: E
}
impl<B, E, P> FnOnce<P> for MutClosure<B,E> where B: Fn<(&mut E, P)> {
type Output = B::Output;
extern "rust-call" fn call_once(self, args: P) -> B::Output {
self.body.call((&mut self.environment, args))
}
}
impl<B, E, P> FnMut<P> for MutClosure<B,E> where B: Fn<(&mut E, P)> {
extern "rust-call" fn call_mut(&mut self, args: P) -> B::Output {
self.body.call((&mut self.environment, args))
}
}
fn foo<T: Default>() {
let foo = ...;
let bar = ...;
hof(|arg1, arg2| { /* closure body */ })
}
fn foo_desugared<T: Default>() {
let foo = ...;
let bar = ...;
// it really doesn't matter if you pass extra early lifetimes in - they are substituted
// early anyway.
fn closure<'π, T: Default>((foo, bar): (&'π mut _, &'π mut _), (arg1, arg2): (_, _)) -> _ {
// closure body
}
hof(MutClosure { body: closure::<T>, environment: (&mut foo, &mut bar) })
//^ note we are creating a MutClosure<fn(...) {closure}, (_, _)>
} |
In other words, closures basically get their type-lifetime-erasing power from fn-items. |
Triage: looks like a discussion that didn't really go anywhere. Not sure what to do. |
Now fn items are always zero-sized, so closures can always be converted to the The fn item "truncation" issue is still present. It's more of a formalization issue than a compiler issue, through, so I'm closing this. |
update FIXME(rust-lang#6298) to point to open issue 15020 update FIXME(rust-lang#6268) to point to RFC 811 update FIXME(rust-lang#10520) to point to RFC 1751 remove FIXME for emscripten issue 4563 and include target in `test_estimate_scaling_factor` remove FIXME(rust-lang#18207) since node_id isn't used for `ref` pattern analysis remove FIXME(rust-lang#6308) since DST was implemented in rust-lang#12938 remove FIXME(rust-lang#2658) since it was decided to not reorganize module remove FIXME(rust-lang#20590) since it was decided to stay conservative with projection types remove FIXME(rust-lang#20297) since it was decided that solving the issue is unnecessary remove FIXME(rust-lang#27086) since closures do correspond to structs now remove FIXME(rust-lang#13846) and enable `function_sections` for windows remove mention of rust-lang#22079 in FIXME(rust-lang#22079) since this is a general FIXME remove FIXME(rust-lang#5074) since the restriction on borrow were lifted
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:
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
andT0...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
andTi
are unused, if a user were to write this, it would require aPhantomData
. This would in turn make OIBIT consider those type parameters to represent reachable data, which might affectSend
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: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 notSend
.There is a similar case in the outlives relation:
Here, we have an early-bound region
'a
in-scope, and hence the type ofx
would correspond toC<'a>
(there are no upvars and no type parameters). However,bar
requires thatC<'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
The text was updated successfully, but these errors were encountered: