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
This issue is a part of #371, but it seems to me this particular design decision warrants its own thread as it is a fairly well-defined self-contained question: should a plain "e1 = e2" really retag all references (and boxes) that are being copied here?
This makes assignment a surprisingly rich operation, and even gives it a non-trivial side-effect. It is the cause of the UB in rust-lang/rust#151728 and probably others.
The main reason we're doing this is to be consistent with function calls: there, we pretty much have to do such retags if we want to add noalias attributes for references that are being passed in fields of structs/enums. But assignments are a lot more common than function calls, so maybe it is justified to special-case function calls? Retags would then basically only happen on function calls and when there are (implicit or explicit) reference-of operators being executed. There's a lot of implicit reference-of operators inserted all over the place, but not in generic code where this issue is particularly nasty since one can easily forget reasoning about the case where T = &mut _/T = Box<_>.
Function returns are different from assignments, so the question also arises for them.
IIUC, function calls are already effectively special cased for protectors, so extending the function argument special case to give retags when simple assignment doesn't seem unreasonable.
My initial baseline intuition would suggest that retags happen at exactly the same set of coercion sites. More precisely, because coercion sites are where captured lifetimes can be coerced, it is consistent to imagine the compiler always retags with that derived lifetime inferred by the lifetime coercion.
On the other hand, assignment is a rich operation -- it drops the previous value in the place. It seems suboptimal if *ptr = value drops the previous value and ptr.write(value) retags the new value without any simple way to neither drop the previous nor retag the new value.
So my initial leaning is to actually go against that intuition I described above and continue doing retags as a part of assignment. Once MaybeDangling exists, that's the ideal way to avoid unwanted retags. (Thus the refined intuition being that coercions (not all coercion sites) do retags, as they're a specific instance of typed moves, which are what do retags.)
It seems suboptimal if *ptr = value drops the previous value and ptr.write(value) retags the new value without any simple way to neither drop the previous nor retag the new value.
Ah, that is a good point... yeah that does seem suboptimal. I thought this can be worked around with ptr.cast::<...>().write(MaybeDangling::new(value))... but that still passes value to a function (MaybeDangling::new) so this would still retag.
So my initial leaning is to actually go against that intuition I described above and continue doing retags as a part of assignment. Once MaybeDangling exists, that's the ideal way to avoid unwanted retags. (Thus the refined intuition being that coercions (not all coercion sites) do retags, as they're a specific instance of typed moves, which are what do retags.)
I can't square that "refined intuition" with your "initial leaning", they still seem to contradict each other?
I think I figured out where my initial intuition went wrong. While I originally attributed my intuition as being that retags would apply at potential coercion sites, I should have instead said that it applies at any surface language reborrow position — that is, any expression position that, given a place expression with type &mut _, would mark the source place as borrowed and not moved from.
That might be an opaque way of saying all value expression positions, tbh; I'm not confident on the precise rules for &mut reborrowing. It's at least every coercion site, but at least assignment to a fresh let is a reborrow site and not a coercion site.
I can't square that "refined intuition" with your "initial leaning", they still seem to contradict each other?
Restating for hopefully improved clarity, the retag operation is done as part of a typed move. Any coercion must therefore result in a retag, as all coercion sites are value expressions.
Retags would then basically only happen on function calls and when there are (implicit or explicit) reference-of operators
I'm working on getting rid of many of those implicit reference operators! rust-lang/rust#164014 The current rules around them are incredibly cursed, e.g. adding lifetime annotations to your code can affect whether one gets inserted. So no-op reborrows (those between identical-modulo-lifetimes types) definitely should not be soundness-relevant.
Activity
CAD97 commentedon Jan 29, 2026
IIUC, function calls are already effectively special cased for protectors, so extending the function argument special case to give retags when simple assignment doesn't seem unreasonable.
My initial baseline intuition would suggest that retags happen at exactly the same set of coercion sites. More precisely, because coercion sites are where captured lifetimes can be coerced, it is consistent to imagine the compiler always retags with that derived lifetime inferred by the lifetime coercion.
On the other hand, assignment is a rich operation -- it drops the previous value in the place. It seems suboptimal if
*ptr = valuedrops the previous value andptr.write(value)retags the new value without any simple way to neither drop the previous nor retag the new value.So my initial leaning is to actually go against that intuition I described above and continue doing retags as a part of assignment. Once
MaybeDanglingexists, that's the ideal way to avoid unwanted retags. (Thus the refined intuition being that coercions (not all coercion sites) do retags, as they're a specific instance of typed moves, which are what do retags.)RalfJung commentedon Jan 30, 2026
Ah, that is a good point... yeah that does seem suboptimal. I thought this can be worked around with
ptr.cast::<...>().write(MaybeDangling::new(value))... but that still passesvalueto a function (MaybeDangling::new) so this would still retag.I can't square that "refined intuition" with your "initial leaning", they still seem to contradict each other?
CAD97 commentedon Mar 9, 2026
I think I figured out where my initial intuition went wrong. While I originally attributed my intuition as being that retags would apply at potential coercion sites, I should have instead said that it applies at any surface language reborrow position — that is, any expression position that, given a place expression with type
&mut _, would mark the source place as borrowed and not moved from.That might be an opaque way of saying all value expression positions, tbh; I'm not confident on the precise rules for
&mutreborrowing. It's at least every coercion site, but at least assignment to a freshletis a reborrow site and not a coercion site.Restating for hopefully improved clarity, the retag operation is done as part of a typed move. Any coercion must therefore result in a retag, as all coercion sites are value expressions.
Jules-Bertholet commentedon Oct 10, 2026
I'm working on getting rid of many of those implicit reference operators! rust-lang/rust#164014 The current rules around them are incredibly cursed, e.g. adding lifetime annotations to your code can affect whether one gets inserted. So no-op reborrows (those between identical-modulo-lifetimes types) definitely should not be soundness-relevant.