-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Comments
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. |
cc @rust-lang/wg-unsafe-code-guidelines -- we have this group just for this sort of thing =) |
I'm 100% behind getting rid of |
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. |
@joshtriplett But you can always use |
Can you give an example of what you mean? You can't do much more with |
Yeah, and that's just as unsafe. So TBH I do not see the point. |
@RalfJung No, it's not, and I've explained why. With EDIT: Now if "references exist" cannot be UB, ever, then this is not a problem, but IIUC, they do. |
It's not actually different from an |
@eddyb Ah okay I see -- that has nothing to with with I agree |
@RalfJung Okay I should make it clearer that the |
If we could weaken ... tough people would probably still turn them into references ASAP. |
How would they instantiate their custom type in stable Rust? User To me this sounds like it would reduce what you can do in the 2018 edition. In the 2015 edition you can create |
@japaric Okay, that's a good point. I removed the bound from the Note that you can use an associated constant if you don't need arguments in your constructor (e.g. Depending on what you're doing you might be able to make your fields public, but as was in the case of cc @Centril @oli-obk Do we want to force people to replace their "custom synchronization abstractions" involving
What kinds of types?
Hmm, Given I wish the |
@rfcbot poll @rust-lang/libs @rust-lang/lang Should we add #[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()
}
} |
Team member @eddyb has asked teams: T-lang, T-libs, for consensus on:
|
@RalfJung What we want is 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. |
In embedded we use // 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 As there are no newtypes involved the user can use primitives and types I'm not against this feature as long as the above pattern continues to work on stable Rust 2018. |
If there's an API that @japaric and libs are happy with, I'm happy to remove |
Personally, I think we should just deprecate |
@japaric Ah, if you're doing code generation, you can have your own version of 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- #[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 Theoretically you can even create a IMO a safe abstraction like this is a good long-term investment, since it can handle any sort of cc @RalfJung Has anything like this been proven safe? |
@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. |
@Nemo157 Ah, that's a very interesting detail! Yeah you can just have one token type per level and use Another idea that I came up with while discussing with @arielb1 on IRC: We could only allow private That, combined with deprecation of private |
IIRC, AVR actually allows for recursive interrupt handlers:
It's up to the programmer to choose (and thus appropriately handle) this case. |
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. |
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 An example of a C library that uses both is |
I don’t like this. It’s already unsafe. That’s good enough. |
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 |
With the lint being tracked in #128794, I think we should close this issue: Nominating for discussion by @rust-lang/lang: should we close this in favor of #128794 or are there still plans to entirely deprecate |
Agreed. Given the degree to which @rfcbot fcp close |
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. |
(@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 I claim that Currently, newcomers often choose Keeping |
I want to second @kpreid's sentiment here: I think we should still eventually remove In my opinion, removing |
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 Many people that use 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. |
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 @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. |
Yes please I could use some bot help :D
|
🔔 This is now entering its final comment period, as per the review above. 🔔 |
@rustbot labels -I-lang-nominated We discussed this in the 2024-10-16 lang triage call, and it's now in FCP (disposition close). |
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..
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 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 We are designing for the user who in the next moment takes time to consider what crimes they want to |
Okay, I've skimmed this thread. I think it's compelling to have a future without Given how we got here, I am comfortable for now with the current story that Closing the gap that remainsThe 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 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 callIn the absence of data, applying judgment here is difficult in that it balances two highly sensitive topics – the reliability of code in Rust, even 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. ConclusionI agree that clippy may want to lint aggressively against Effectively this checkbox is a vote for postponement, but the distinction isn't important enough for me to restart the FCP now. @rfcbot reviewed |
Note that there is one aspect of static mut S: &mut [i32] = &mut [0, 1, 2]; This is an instance of lifetime extension, which we generally reject for 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
}; |
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. |
static mut
is almost impossible to use correctly, see rust-lang-nursery/lazy-static.rs#117 for an example in the widely-usedlazy-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:And then use
CustomSynchronizingAbstraction
with regularstatic
s, 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
. Butstatic mut
is not even soft-deprecated currently.The text was updated successfully, but these errors were encountered: