You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Should closures implicitly be wrapped in MaybeDangling? #511
Closures can have fields that are reference-typed, and then when closures are passed around by-value, those fields behave like struct fields for the aliasing model, so they get retagged and protected. This can lead to subtle and surprising UB. For instance:
Given that the user never sees the reference-typed field here, this seems like quite the footgun. So maybe we should declare that closures behave like MaybeDangling, and not recurse into them when retagging? That would also mean we have to stop emitting noalias dereferenceable for references passed in closure fields.
My immediate gut reaction was that it should be fine to retag — the closure captures a lifetime and code should be aware of that when moving the closure object around — but these examples are pretty illustrative; it isn't really about the retag but about the protector.
It's also a bit different if the reference comes from by-ref capture of a local place (this should as much as possible behave as if directly using the local imo) or if it's by-move capture of a ref retagging type (which is moreso what my gut reaction was considering).
The example with fetch_update is a bit like the Arc dealloc case, except instead of just &ControlBlock we have (&ControlBlock, &GuardedData). Only ControlBlock gets the UnsafeCell exception to protectors, and not GuardedData.
Somewhat amusing observation: that example would've been fine if the closure captured last_next, but it actually captured the projected place *last_next instead, IIUC. This is deliberate behavior to allow closure captures to participate in precise partial place borrowing, but resulted in a problematic protector here.
Potentially wild concept — would it be possible to retag but not protect any retagging types captured by closures (or equivalently async)? Although in fairness, IIRC "dereferencable_on_entry" isn't actually all that useful an optimization property.
Somewhat amusing observation: that example would've been fine if the closure captured last_next, but it actually captured the projected place *last_next instead, IIUC. This is deliberate behavior to allow closure captures to participate in precise partial place borrowing, but resulted in a problematic protector here.
Good point! I had not realized this.
Potentially wild concept — would it be possible to retag but not protect any retagging types captured by closures (or equivalently async)? Although in fairness, IIRC "dereferencable_on_entry" isn't actually all that useful an optimization property.
Without a protector, we can't even emit noalias, as LLVM considers that to be function-scoped.
I actually separately noted this, because I want to turn a macro like boxed!($expr) into (E2021 rules) construct_in_place(ptr, FnOnce::call_once, (|| $expr,)), but then you get a bunch of retags that wouldn't happen in if expr was evaluated directly.
Although in fairness, IIRC "dereferencable_on_entry" isn't actually all that useful an optimization property.
I personally have plans to make use of a dereferenceable that means dereferenceable_on_entry. The optimizations require different tracing, but should still be useful.
As far as the original question here is concerned -- we tried the implicit MaybeDangling for closures and reverted it in rust-lang/rust#162361.
As part of that discussion I had entirely forgotten about rust-lang/miri#3613. That is a very unfortunate interaction indeed, fetch_update really doesn't want its closure argument to be retagged. That could can fairly easily use raw pointers instead of references, but it's hard to know that one has to do that.
Activity
CAD97 commentedon May 20, 2024
My immediate gut reaction was that it should be fine to retag — the closure captures a lifetime and code should be aware of that when moving the closure object around — but these examples are pretty illustrative; it isn't really about the retag but about the protector.
It's also a bit different if the reference comes from by-ref capture of a local place (this should as much as possible behave as if directly using the local imo) or if it's by-move capture of a ref retagging type (which is moreso what my gut reaction was considering).
The example with
fetch_updateis a bit like theArcdealloc case, except instead of just&ControlBlockwe have(&ControlBlock, &GuardedData). OnlyControlBlockgets theUnsafeCellexception to protectors, and notGuardedData.Somewhat amusing observation: that example would've been fine if the closure captured
last_next, but it actually captured the projected place*last_nextinstead, IIUC. This is deliberate behavior to allow closure captures to participate in precise partial place borrowing, but resulted in a problematic protector here.Potentially wild concept — would it be possible to retag but not protect any retagging types captured by closures (or equivalently
async)? Although in fairness, IIRC "dereferencable_on_entry" isn't actually all that useful an optimization property.RalfJung commentedon May 21, 2024
Good point! I had not realized this.
Without a protector, we can't even emit
noalias, as LLVM considers that to be function-scoped.chorman0773 commentedon Jun 8, 2024
I actually separately noted this, because I want to turn a macro like
boxed!($expr)into (E2021 rules)construct_in_place(ptr, FnOnce::call_once, (|| $expr,)), but then you get a bunch of retags that wouldn't happen in if expr was evaluated directly.I personally have plans to make use of a dereferenceable that means dereferenceable_on_entry. The optimizations require different tracing, but should still be useful.
RalfJung commentedon Sep 22, 2024
Here's another case of someone hitting this problem.
cuviper commentedon Oct 25, 2026
I believe rayon has the same problem too: rayon-rs/rayon#1328
RalfJung commentedon Oct 26, 2026
As far as the original question here is concerned -- we tried the implicit MaybeDangling for closures and reverted it in rust-lang/rust#162361.
As part of that discussion I had entirely forgotten about rust-lang/miri#3613. That is a very unfortunate interaction indeed,
fetch_updatereally doesn't want its closure argument to be retagged. That could can fairly easily use raw pointers instead of references, but it's hard to know that one has to do that.