Skip to content
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

Consider deprecation of UB-happy static mut #53639

Closed
eddyb opened this issue Aug 23, 2018 · 212 comments
Closed

Consider deprecation of UB-happy static mut #53639

eddyb opened this issue Aug 23, 2018 · 212 comments
Labels
A-maybe-future-edition Something we may consider for a future edition. C-enhancement Category: An issue proposing an enhancement or a PR with one. C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such disposition-close This PR / issue is in PFCP or FCP with a disposition to close it. finished-final-comment-period The final comment period is finished for this PR / Issue. needs-rfc This change is large or controversial enough that it should have an RFC accepted before doing it. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@eddyb
Copy link
Member

eddyb commented Aug 23, 2018

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.

@eddyb eddyb added I-nominated T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Aug 23, 2018
@Centril
Copy link
Contributor

Centril commented Aug 23, 2018

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.

@nikomatsakis
Copy link
Contributor

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

@alercah
Copy link
Contributor

alercah commented Aug 23, 2018

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.

@joshtriplett
Copy link
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.

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

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

@alercah
Copy link
Contributor

alercah commented Aug 23, 2018

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.

@RalfJung
Copy link
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.

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

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

@alercah
Copy link
Contributor

alercah commented Aug 23, 2018

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?

@RalfJung
Copy link
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.

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

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

@RalfJung
Copy link
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.

@japaric
Copy link
Member

japaric commented Aug 23, 2018

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

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

@japaric Okay, that's a good point. I removed the bound from the struct definition, does that help? If you use it with a T: !Sync type you just can't put it in a static, but it should be fine otherwise.

Note that you can use an associated constant if you don't need arguments in your constructor (e.g. lazy-static already uses Lazy::<T>::INIT for this pattern).
Otherwise, yes, the min_const_fn requirement is a problem.

Depending on what you're doing you might be able to make your fields public, but as was in the case of lazy-static, that's not always the case.

cc @Centril @oli-obk Do we want to force people to replace their "custom synchronization abstractions" involving static muts with ones that require const fn?

In the 2015 edition you can create static mut variables that contain primitive types

What kinds of types? Atomics with relaxed ordering should probably be preferred either way even in single-threaded code, unless you have some sort of proof that interrupts are disabled.
(I'm assuming that you just can't trust anything to be single-threaded in user-space, in a library, and in kernel-space / embedded you have interrupts to worry about, wrt reentrance-safety)

like adding a RacyCell type with const constructor to core

Hmm, RacyCell<PubliclyConstructableType> would be okay, I think, you'd just have to implement methods on it (so you'd wrap it in another publicly-constructible-type, or use an extension trait).

Given pub struct Abstraction(pub RacyCell<AbstractionData>);, I think that &'static Abstraction is significantly safer than static mut but only if it gives you a *mut AbstractionData, not a &mut.

I wish the min_const_fn idea popped up sooner and we stabilized it already 😢.
Another 3 years of randomly finding unsound static mut uses doesn't sound fun.

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

@rfcbot poll @rust-lang/libs @rust-lang/lang Should we add RacyUnsafeCell?

#[repr(transparent)]
pub struct RacyUnsafeCell<T>(UnsafeCell<T>);

unsafe impl<T: Sync> Sync for RacyUnsafeCell<T> {}

impl<T> RacyUnsafeCell<T> {
    pub const fn new(x: T) -> Self {
        RacyUnsafeCell(UnsafeCell::new(x))
    }
    pub fn get(&self) -> *mut T {
        self.0.get()
    }
}

@rfcbot
Copy link

rfcbot commented Aug 23, 2018

Team member @eddyb has asked teams: T-lang, T-libs, for consensus on:

Should we add RacyUnsafeCell?

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

@RalfJung What we want is &'static Abstraction, I think, and *mut only in methods of it.

That is, the user of the abstraction should be outside of the "unsafe zone" and be able to use only safe code to interact with the abstraction, otherwise it's not really a good abstraction.

@japaric
Copy link
Member

japaric commented Aug 23, 2018

@eddyb

In embedded we use static mut variables with interrupt handlers. These
handlers are invoked by the hardware and non-reentrant by hardware design (while
executing an interrupt handler the same handler will not be invoked again if
the interrupt signal is received. Also the devices are single core). So we have a
pattern like to let users add state to their interrupt handlers:

// we generate this function using macros
#[export_name = "SOME_KNOWN_NAME_TO_HAVE_THE_LINKER_PUT_THIS_IN_THE_RIGHT_PLACE"]
pub unsafe extern "C" fn interrupt_handler_that_has_some_random_name_unknown_to_the_user() {
    static mut STATE: usize = 0; // initial value provided by the user

    on_button_pressed(&mut STATE);
}

// the user provides this function along with the initial value for the state
fn on_button_pressed(count: &mut usize) {
    *count += 1;
    println!("Button has been pressed {} times", *count);
}

This way the end user indirectly uses static mut variables in a non-reentrant
fashion. The user can't call the interrupt handler themselves because the name
of the function is an implementation detail and if they call on_button_pressed
themselves there's still no problem because that won't operate on the hidden
static mut variable.

As there are no newtypes involved the user can use primitives and types
the define themselves (e.g. structs with pub(crate) fields) without requiring
the unstable const fn feature.

I'm not against this feature as long as the above pattern continues to work on stable Rust 2018.

@scottmcm
Copy link
Member

If there's an API that @japaric and libs are happy with, I'm happy to remove static mut.

@nikomatsakis
Copy link
Contributor

Personally, I think we should just deprecate static mut across the board, but not remove it from Rust 2018. We don't gain much by making it a hard removal, really -- we don't want to "make space" for some other meaning.

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

@japaric Ah, if you're doing code generation, you can have your own version of RacyUnsafeCell with a public field and no const fn constructor, would that work for you?
UnsafeCell being !Sync isn't necessary, but it does avoid forgetting to impl !Sync.


There's a more powerful pattern I prefer using in those situations:

// Lifetime-invariant ZST token. Probably doesn't even need the invariance.
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct InterruptsDisabled<'a>(PhantomData<&'a mut &'a ()>);

// Public field type to get around the lack of stable `const fn`.
pub struct InterruptLock<T: ?Sized + Send>(pub UnsafeCell<T>);

// AFAIK this should behave like Mutex.
unsafe impl<T: ?Sized + Send> Sync for InterruptLock<T> {}

impl<T: ?Sized + Send> InterruptLock<T> {
    // This gives access to the `T` that's `Send` but maybe not `!Sync`,
    // for *at most* the duration that the interrupts are disabled for.
    // Note: I wanted to implement `Index` but that can't constrain lifetimes.
    fn get<'a>(&'a self, _: InterruptsDisabled<'a>) -> &'a T {
        unsafe { &*self.0.get() }
    }
}

// Note that you bake any of these into `InterruptLock` without `const fn`,
// because you would need a private field to ensure nobody can touch it.
// OTOH, `UnsafeCell<T>` makes *only constructing* the field public.
pub type InterruptCell<T: ?Sized + Send> = InterruptLock<Cell<T>>;
pub type InterruptRefCell<T: ?Sized + Send> = InterruptLock<RefCell<T>>;
#[export_name = "SOME_KNOWN_NAME_TO_HAVE_THE_LINKER_PUT_THIS_IN_THE_RIGHT_PLACE"]
pub unsafe extern "C" fn interrupt_handler_that_has_some_random_name_unknown_to_the_user(
    token: InterruptsDisabled,
) {
    on_button_pressed(token);
}

static COUNT: InterruptCell<usize> = InterruptLock(UnsafeCell::new(
    Cell::new(0),
));
fn on_button_pressed(token: InterruptsDisabled) {
    let count = COUNT.get(token);
    count.set(count.get() + 1);
    println!("Button has been pressed {} times", count.get());
}

There's another variation on this, where you make the token non-Copy and can do:

#[export_name = "SOME_KNOWN_NAME_TO_HAVE_THE_LINKER_PUT_THIS_IN_THE_RIGHT_PLACE"]
pub unsafe extern "C" fn interrupt_handler_that_has_some_random_name_unknown_to_the_user(
    token: InterruptsDisabled,
) {
    on_button_pressed(token);
}

static COUNT: InterruptLock<usize> = InterruptLock(UnsafeCell::new(
    0,
));
fn on_button_pressed(mut token: InterruptsDisabled) {
    // This mutable borrow of `token` prevents using it for
    // accessing any `InterruptLock`s, including `COUNT` itself.
    // The previous version was like `&token` from this one.
    let count = COUNT.get_mut(&mut token);
    *count += 1;
    println!("Button has been pressed {} times", *count);
}

(I didn't write the implementation details for the second version for brevity's sake, if desired I can edit this later)

You can even allow creating a InterruptsDisabled out of a &mut InterruptsDisabled, effectively sub-borrowing it (assuming you want to keep it ZST so it stays zero-cost).
Then you can combine the two versions by allowing the creation of the first version from a &InterruptsDisabled from the second version (but, again, only to keep it ZST).

Theoretically you can even create a InterruptsDisabled using HRTB and calling a closure after disabling interrupts, but this only works for the first version, not the second, stronger, one.
Also, IIRC, on some platforms it's impossible to truly turn all interrupts off.


IMO a safe abstraction like this is a good long-term investment, since it can handle any sort of Send + !Sync data that's accessed only from within interrupts.

cc @RalfJung Has anything like this been proven safe?

@Nemo157
Copy link
Member

Nemo157 commented Aug 23, 2018

@eddyb note that the non-reentrancy that @japaric describes is for that particular interrupt handler (and any lower priority, but that’s hard to do anything useful with). Interrupts are not globally disabled during an interrupt handler on architectures like ARMv6-M. It might be possible to use a more complicated token scheme where each interrupt has its own token type, but I don’t know if anyone has looked into some way to make that actually usable.

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

@Nemo157 Ah, that's a very interesting detail! Yeah you can just have one token type per level and use From impls to connect them up or something (and the methods in InterruptLock<Level, T> would take impl Into<InterruptsDisabled<Level, 'a>> instead).


Another idea that I came up with while discussing with @arielb1 on IRC:

We could only allow private static mut. So you can keep it e.g. in a module, or within a block inside a fn, const, or another static, etc. - but not export it out of there.

That, combined with deprecation of private static mut, could improve the situation.

@shepmaster
Copy link
Member

In embedded we use static mut variables with interrupt handlers. These
handlers are invoked by the hardware and non-reentrant by hardware design

IIRC, AVR actually allows for recursive interrupt handlers:

#define ISR_NOBLOCK
ISR runs with global interrupts initially enabled. The interrupt enable flag is activated by the compiler as early as possible within the ISR to ensure minimal processing delay for nested interrupts.

This may be used to create nested ISRs, however care should be taken to avoid stack overflows, or to avoid infinitely entering the ISR for those cases where the AVR hardware does not clear the respective interrupt flag before entering the ISR.

Use this attribute in the attributes parameter of the ISR macro.

It's up to the programmer to choose (and thus appropriately handle) this case.

@whitequark
Copy link
Member

IIRC, AVR actually allows for recursive interrupt handlers:

On every architecture I'm aware of, you can explicitly set the interrupt flag after entering a handler; this is not AVR-specific. (On architectures with interrupt prioritization, like Cortex-M, you'll also need to manipulate priorities.) But the point here is that this needs to be done explicitly, with unsafe code; the architecture guarantees that you wouldn't reenter the ISR if you don't specifically request that.

So this is perfectly in line with the usual safety rules.

@gnzlbg
Copy link
Contributor

gnzlbg commented Oct 24, 2018

Some C APIs expose mutable globals that my Rust code right now access via:

extern "C" {
    pub static mut FOO: BAR;
}

Some C APIs require their users to define mutable globals, that the C library then accesses. Right now my Rust code interfaces with those using:

#[export(name = "foo")]
pub static mut FOO: BAR = ..initializer..;

How do I access mutable globals from C libraries, and how do I provide mutable globals to C libraries, without static mut ?

An example of a C library that uses both is jemalloc.

@alexreg
Copy link
Contributor

alexreg commented Oct 24, 2018

I don’t like this. It’s already unsafe. That’s good enough.

@uazu
Copy link

uazu commented Jun 6, 2024

Keeping DeferrerAux a zero-sized type ...

It seems an awkward solution, i.e. an UnsafeCell containing the thread-ID and another UnsafeCell, but with a backdoor that allows the check to be bypassed if the wrapper "knows" that the check has already been done. I agree that it meets the requirements, though, and looks like it won't break any rules. If the decision is made to go ahead and deprecate static mut, then that's what I'll implement. Thanks.

@RalfJung
Copy link
Member

With the lint being tracked in #128794, I think we should close this issue: static mut in general is actually "fine", it's just 'static references that are so dangerous. Global mutable state is inherently dangerous, but it doesn't get fundamentally safer by using static S: SyncUnsafeCell instead of static mut. There might be some benefit to using &'static SyncUnsafeCell references instead of raw pointers, but not enough to fully deprecate static mut.

Nominating for discussion by @rust-lang/lang: should we close this in favor of #128794 or are there still plans to entirely deprecate static mut?

@RalfJung RalfJung added the I-lang-nominated Nominated for discussion during a lang team meeting. label Aug 26, 2024
@traviscross
Copy link
Contributor

traviscross commented Aug 26, 2024

Agreed. Given the degree to which deny-by-default linting against potentially long-lived references to static mut items is going to resolve the underlying problem here, and given that there seem to be compelling use cases, it's difficult for me to imagine that we'd soon deprecate or remove static mut itself. So, on that basis, I propose...

@rfcbot fcp close

@rfcbot
Copy link

rfcbot commented Aug 26, 2024

Team member @traviscross has proposed to close this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-close This PR / issue is in PFCP or FCP with a disposition to close it. labels Aug 26, 2024
@kpreid
Copy link
Contributor

kpreid commented Aug 26, 2024

(@RalfJung suggested I mention my opinion here when I brought it up on Zulip. Disclaimer: I haven't read the entire history of this specific issue; I've been aware of other discussions of static mut and SyncUnsafeCell.)

I claim that static mut should be deprecated and eventually removed in future editions, in favor of cell types that can be placed in a non-mutable static. (That is: this issue should not be closed.) This is not because it is intrinsically better — rewriting a program to use SyncUnsafeCell does not improve that program — but to improve the ergonomics of the language considered as a whole; removing static mut would guide users towards handling their mutable global state better.

Currently, newcomers often choose static mut because it's a natural combination of language features they are already familiar with (mut bindings and static items). If, instead, static mut did not exist, and they had to combine static with some cell type, then they are necessarily prompted to think about which cell type is appropriate (SyncUnsafeCell, Mutex, OnceCell, LazyLock…), and the documentation of each such cell type can tell them how to use it and when to use it.

Keeping static mut around is a tripping hazard; it makes users fall into unsoundness. Let them come to a well-signposted fork in the road, instead.

@jamesmunns
Copy link
Member

I want to second @kpreid's sentiment here: I think we should still eventually remove static mut for the reasons listed here (like not requiring Sync!), and provide types like SyncUnsafeCell, or potentially even other "harm reduction" unsafe types (like I am working on in the grounded crate for the embedded ecosystem particularly) that have more clearly defined and documented SAFETY invariants.

In my opinion, removing static mut makes the language more consistent, even if we end up pointing people to other types that better match the kind of things they are reaching for.

@RalfJung
Copy link
Member

RalfJung commented Aug 26, 2024

Sometimes, you need global mutable state. We already had several cases of people being quite confused and concerned by the lints we started emitting, and it turns out that what they were doing is completely fine and we basically told them to add a bit of noise to their code to silence the lint. Those people will be even more confused if we now tell them that static mut will be banned entirely, and I am not convinced that replacing addr_of_mut!(STATIC) with STATIC.get() is really doing anything for ensuring the safety of global mutable state.

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

So overall I am not convinced that the churn caused on existing code, and the overhead caused for people that know what they are doing, is balanced by a sufficient risk reduction for people that don't know what they are doing.

@scottmcm
Copy link
Member

I'm happy for the foreseeable future about the linting plan we've arrived at. With a time machine I agree I'd just nuke static mut entirely, but I don't know that we get enough value from changing it now, especially since semantically they end up just being a *mut. And it's also completely fine for clippy to lint fairly aggressively about non-trivial uses of static mut, or uses with certain types where there are good alternatives.

@rfcbot reviewed

Maybe in another 5 years we might revisit something like this again, but I don't think it's something we need to keep open.

@nikomatsakis
Copy link
Contributor

@rfcbot reviewed

@RalfJung put it well.

(In fact, as I was typing this message, I originally had @ralfbot reviewed...maybe we need one of those?)

@RalfJung
Copy link
Member

RalfJung commented Oct 16, 2024 via email

@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Oct 16, 2024
@rfcbot
Copy link

rfcbot commented Oct 16, 2024

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

@traviscross
Copy link
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).

@rustbot rustbot removed the I-lang-nominated Nominated for discussion during a lang team meeting. label Oct 16, 2024
@tmandry
Copy link
Member

tmandry commented Oct 24, 2024

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.

@tmandry
Copy link
Member

tmandry commented Oct 24, 2024

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

@RalfJung
Copy link
Member

RalfJung commented Oct 24, 2024

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
};

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Oct 26, 2024
@rfcbot
Copy link

rfcbot commented Oct 26, 2024

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.

@traviscross traviscross closed this as not planned Won't fix, can't repro, duplicate, stale Oct 27, 2024
@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Oct 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-maybe-future-edition Something we may consider for a future edition. C-enhancement Category: An issue proposing an enhancement or a PR with one. C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such disposition-close This PR / issue is in PFCP or FCP with a disposition to close it. finished-final-comment-period The final comment period is finished for this PR / Issue. needs-rfc This change is large or controversial enough that it should have an RFC accepted before doing it. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
Status: Rejected/Not lang
Development

No branches or pull requests