Skip to content

Consider deprecation of UB-happy static mut #53639

Closed as not planned
Closed as not planned

Description

@eddyb

static mut is almost impossible to use correctly, see rust-lang-nursery/lazy-static.rs#117 for an example in the widely-used lazy-static.

You must be able to show that every borrow of the static mut is not reentrant (as opposed to regular interior mutability, which only requires reentrance-freedom when accessing data), which is almost entirely impossible in real-world scenarios.


We have a chance at removing it from Rust2018 and force people to use a proper synchronization abstraction (e.g. lazy_static! + Mutex), or in lieu of one, thread_local! / scoped_thread_local!.

If they were using static mut with custom synchronization logic, they should do this:

pub struct CustomSynchronizingAbstraction<T> {
    /* UnsafeCell / Cell / RefCell / etc. around data, e.g. `T` */
}
// Promise that proper synchronization exists *around accesses*.
unsafe impl<T: Sync> Sync for CustomSynchronizingAbstraction<T> {}

And then use CustomSynchronizingAbstraction with regular statics, safely.

This matches the "soundness boundary" of Rust APIs, whereas static mut is more like C.

cc @RalfJung @rust-lang/compiler @rust-lang/lang


2023-08 Note from triage, many years later: there's now https://doc.rust-lang.org/1.71.0/std/cell/struct.SyncUnsafeCell.html in the library, added by #95438, which can be used instead of static mut. But static mut is not even soft-deprecated currently.

Activity

  1. Centril commented on Aug 23, 2018

    @Centril
    Contributor

    I don't know at all whether we should do this or not atm. But to make this decision I have an...

    ...Idea: We should do a crater run to scrutinize how many legitimate usages there are and how many ones there are that have UB.

  2. nikomatsakis commented on Aug 23, 2018

    @nikomatsakis
    Contributor

    cc @rust-lang/wg-unsafe-code-guidelines -- we have this group just for this sort of thing =)

  3. alercah commented on Aug 23, 2018

    @alercah

    I'm 100% behind getting rid of static mut. Any attempt to use it which isn't UB is replaceable with something else which is safe, likely a Mutex or something thread local. Or, per the original comment, a custom alternate type implementing Sync directly.

  4. joshtriplett commented on Aug 23, 2018

    @joshtriplett
    Member

    We should clearly document the undefined behavior, but I don't think we should remove the ability to have (unsafe) global mutable locations directly, without any layer of indirection. That would make it more difficult to build certain types of lock-free synchronization, for instance.

  5. eddyb commented on Aug 23, 2018

    @eddyb
    ContributorAuthor

    @joshtriplett But you can always use unsafe impl Sync + static?

  6. alercah commented on Aug 23, 2018

    @alercah

    Can you give an example of what you mean? You can't do much more with static mut than you could with a static UnsafeCell.

  7. RalfJung commented on Aug 23, 2018

    @RalfJung
    Member

    But you can always use unsafe impl Sync + static?

    Yeah, and that's just as unsafe. So TBH I do not see the point.

  8. eddyb commented on Aug 23, 2018

    @eddyb
    ContributorAuthor

    @RalfJung No, it's not, and I've explained why. With unsafe impl Sync, you only have to prove the data accesses correct, but with static mut, the references themselves can conflict.

    EDIT: Now if "references exist" cannot be UB, ever, then this is not a problem, but IIUC, they do.
    Also, you can't use a static mut from safe code, only a static. If the static mut is used correctly, why spread unsafety when you can wrap it in a sound API.

  9. alercah commented on Aug 23, 2018

    @alercah

    It's not actually different from an UnsafeCell if we replace each &var with unsafe {&*var.get()}, right? Then the aliasing rules would be the same; the difference is that with the UnsafeCell you can still keep references to the cell itself?

  10. RalfJung commented on Aug 23, 2018

    @RalfJung
    Member

    @eddyb Ah okay I see -- that has nothing to with with unsafe impl tough and everything with &UnsafeCell.

    I agree &UnsafeCell is safer than static mut.

  11. eddyb commented on Aug 23, 2018

    @eddyb
    ContributorAuthor

    @RalfJung Okay I should make it clearer that the unsafe impl is for "custom synchronization abstraction". I'll go edit the issue description.

  12. RalfJung commented on Aug 23, 2018

    @RalfJung
    Member

    If we could weaken static mut to only give you raw pointers, that could help...

    ... tough people would probably still turn them into references ASAP.

  13. japaric commented on Aug 23, 2018

    @japaric
    Contributor

    If they were using static mut with custom synchronization logic, they should do this:

    How would they instantiate their custom type in stable Rust? User cons fns are unstable and even if we do stabilize min_const_fn that doesn't include anything that has bounds so even with that your example won't compile on stable.

    To me this sounds like it would reduce what you can do in the 2018 edition. In the 2015 edition you can create static mut variables that contain primitive types but in the 2018 edition you can not do the equivalent unless I'm missing something like adding a RacyCell type with const constructor to core. (Here I'm assuming that it's not certain whether min_const_fn will make it into the edition release).

  14. 228 remaining items

  15. removed
    proposed-final-comment-periodProposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off.
    on Oct 16, 2024
  16. rfcbot commented on Oct 16, 2024

    @rfcbot

    🔔 This is now entering its final comment period, as per the review above. 🔔

  17. traviscross commented on Oct 16, 2024

    @traviscross
    Contributor

    @rustbot labels -I-lang-nominated

    We discussed this in the 2024-10-16 lang triage call, and it's now in FCP (disposition close).

  18. removed
    I-lang-nominatedNominated for discussion during a lang team meeting.
    on Oct 16, 2024
  19. tmandry commented on Oct 24, 2024

    @tmandry
    Member

    Overall I think I'm aligned with @RalfJung on the idea that for features requiring unsafe, we should default to trusting our users when we aren't creating surprise and when any alternative come with undue ergonomic burdens. That said, there was one thing that I didn't find myself aligned with..

    Many people that use static mut now are entirely aware of what they are doing, they didn't accidentally stumble into global mutable state. People that just mindlessly run into static mut will equally mindlessly start using SyncUnsafeCell (probably by copying some online resource telling them how to silence that annoying borrow checker, or by following helpful compiler hints).

    I mostly agree with the first sentence, though I would observe that even those people who "know what they are doing" are subject to lapses in judgment when coding in anger. I disagree with the second. I think having "speed bumps" like writing SyncUnsafeCell can be an effective way of forcing someone to slow down and consider the implications of what they are doing. Overall I don't think we are ready to commit to that story to the point of deprecating static mut for reasons I'll explain in a follow-up comment, but I still see value in it.

    While it's true that there is a class of users that will mindlessly copy from an online resource, it is ultimately their right to consider the risk profile of what they are doing and override warning signs along the way. There's nothing wrong if someone trying to get Rust running on a development board commits heinous safety violations in their impassioned campaign for blinky LEDs. They have a simple enough problem and hopefully enough expertise that they can reason about the rules they are bending, and we are not primarily designing for that user in that moment. Neither are we designing for the user who is ultimately reckless and hasn't yet learned that the indiscriminate use of unsafe can cost them dearly later on.

    We are designing for the user who in the next moment takes time to consider what crimes they want to commit (in their repository) and the user who is, for one reason or another, willing to use unsafe but a bit more risk averse than the first.

  20. tmandry commented on Oct 24, 2024

    @tmandry
    Member

    Okay, I've skimmed this thread. I think it's compelling to have a future without static mut and where there is a clear replacement. SyncUnsafeCell might be that replacement, though it is a bit fiddly compared to the ergonomics of static mut today.

    Given how we got here, I am comfortable for now with the current story that static mut gives you a *mut and that the same caveats that apply to any *mut apply here – with the caveat that it is probably less forgiving of temporary lapses in judgment, since it's so easily accessed from multiple threads and from different places in your code. We are still in a much better place today than we were; with the lint, we have removed almost any possibility of surprising UB.

    Closing the gap that remains

    The remaining "gap" is about weighing the cost in ergonomics for those who know what they are doing, plus the cost of a transition, against the risk of accidental unsound code. I can see this being a difficult gap to close. On one side we could improve ergonomics of SyncUnsafeCell (or some equivalent), though I don't see any proposals on the table that seem ready to be accepted, and/or do an automated transition across an edition boundary. On the other side we could collect data that says enough people still write unsound code that this is a problem (or that they don't, but that is difficult with any of the ways we collect such data today; absence of evidence isn't evidence of absence).

    The default outcome is that we never close the gap. In the absence of data that this is a real-world issue (even though I suspect it will be), that default outcome is probably correct.

    Making a judgment call

    In the absence of data, applying judgment here is difficult in that it balances two highly sensitive topics – the reliability of code in Rust, even unsafe code, and the ergonomics of unsafe code, aka "getting things done". Both, in my view, are critical for Rust's success.

    At a higher level, the data have says that Rust is far more reliable than just about any alternative, but less ergonomic than its alternatives when it comes to low-level systems tasks. I suspect this is in the back of the minds of other lang team members checking their boxes. Given my opinion that we should lean toward making design decisions on the basis of code rather than abstract properties of a language, this is useful data to base a decision on. Coupled with the cost of making a transition, defaulting to the status quo seems like the right course of action for now.

    Conclusion

    I agree that clippy may want to lint aggressively against static mut. I also agree that we might want to revisit this question in a future with better mechanisms for decomposing safety guarantees. For now though, my impression is that we have solved the most important problem we've observed in the ecosystem, and we should wait for more data or more progress in other areas before going further.

    Effectively this checkbox is a vote for postponement, but the distinction isn't important enough for me to restart the FCP now.

    @rfcbot reviewed

  21. RalfJung commented on Oct 24, 2024

    @RalfJung
    Member

    Note that there is one aspect of static mut that doesn't currently have any alternative. It's somewhat of an accidental feature I think (I wasn't around when this got permitted), and it's unclear whether and how we'd best provide a "nicer" alternative:

    static mut S: &mut [i32] = &mut [0, 1, 2];

    This is an instance of lifetime extension, which we generally reject for &mut in the outermost scope of a static/const (where it "extends" things to 'static lifetime) -- except in static mut where we do accept this, and let it implicitly create a global mutable allocation holding the [0, 1, 2].

    I'm not happy that we accept this, but we don't have any alternative we can migrate existing users to either -- except for the super verbose one:

    static mut S: &mut [i32] = {
        static mut _TMP: [i32; 3] = [0, 1, 2];
        &mut _TMP
    };
  22. added
    to-announceAnnounce this issue on triage meeting
    and removed
    final-comment-periodIn the final comment period and will be merged soon unless new substantive objections are raised.
    on Oct 26, 2024
  23. rfcbot commented on Oct 26, 2024

    @rfcbot

    The final comment period, with a disposition to close, as per the review above, is now complete.

    As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

  24. ehuss commented on Jan 13, 2025

    @ehuss
    Contributor

    Something that came up was some concerns about educating people on what to do if they hit the static_mut_refs lint (tracked in #123059). I have opened rust-lang/edition-guide#354 with a first draft with some recommendations. I would appreciate if anyone could look it over and provide suggestions for things to fix/change/add (or if anything seemed unclear).

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-maybe-future-editionSomething we may consider for a future edition.C-enhancementCategory: An issue proposing an enhancement or a PR with one.C-optimizationCategory: An issue highlighting optimization opportunities or PRs implementing suchT-langRelevant to the language teamdisposition-closeThis PR / issue is in PFCP or FCP with a disposition to close it.finished-final-comment-periodThe final comment period is finished for this PR / Issue.needs-rfcThis change is large or controversial enough that it should have an RFC accepted before doing it.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions