Skip to content

Should regular assignments really retag? #601

Open

Description

@RalfJung

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.

Activity

  1. added
    A-aliasing-modelTopic: Related to the aliasing model (e.g. Stacked/Tree Borrows)
    on Jan 28, 2026
  2. CAD97 commented on Jan 29, 2026

    @CAD97
    Contributor

    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.)

  3. RalfJung commented on Jan 30, 2026

    @RalfJung
    MemberAuthor

    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?

  4. CAD97 commented on Mar 9, 2026

    @CAD97
    Contributor

    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.

  5. Jules-Bertholet commented on Oct 10, 2026

    @Jules-Bertholet

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-aliasing-modelTopic: Related to the aliasing model (e.g. Stacked/Tree Borrows)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions