I think after most of the ecosystem has migrated there isn't a reason to keep a redundant footgun in your back pocket just in case, the 3 year period seems sufficient to me to give a chance to migrate off. Removing it also stops beginners from falling into using it, also, relieving this technical debt is both useful to the language itself and sets a precedent to measure and balance future changes off of.
As a mitigating factor, couldn't taking a &mut
of a static mut be made a compile time error? At least in cases where static analysis can pinpoint where exactly a &mut
is pointing to.
Mitigating of what? Could you explain further?
We're basically stuck with static mut
in the old editions anyway, so removing it fully doesn't help as much as one might wish with actually getting rid of it entirely or simplifying the compiler.
I don't get what the connection you're trying to make between static mut
's presence in previous editions prior to 2027 and presence in editions 2027 is. Going back and removing static mut
retroactively isn't happening, of course, but I'm not sure why that has a significant effect on removing it after a certain point.
Just a cost-benefit thing.
With a time machine, would I remove static mut
in 2014? Absolutely.
But given that the compiler has to support it, that the spec needs to define what it does -- especially if a 2030 crate uses a 2018 crate -- and that the parser needs to parse it under #[cfg]
, it's not necessarily all that useful to remove it from the later editions. Especially if the big footgun is gone.
Let me turn the question around: given that every edition must support using static mut
, what benefit is there to forbidding new editions from defining static mut
?
Warn by default, sure. Maybe even deny by default, though that seems unnecessary if we add the currently discussed extra safety guards around static mut
. But forbid by default or a hard error don't have any value add to offer.
Oh I understand now. Yes, we'd have to put #[cfg]
in the parser, but if the section in the spec about static mut
is going to be written in any case I don't think that there's too much extra cost to defining cross-edition behaviour. It might take some time to discuss and agree on how it behaves but that's expected for a language specification I think.
My intention was pushing people as far away from the pitfall as possible. If you can still use your FFI code nearly as-is with a cargo fix
meant for 1 or 2 editions ago and just allow a lint to get by using it why switch at all? To be clear: it's not wrong to keep static mut
, but I think we shouldn't if we can help it just because of how dangerous it is now and how limited and redundant it'll (probably) become.
The replacements are just as dangerous, and require locking and unsafe
in all the same places. If they weren't just as dangerous, they wouldn't be sufficient. SyncUnsafeCell
perhaps clusters the documentation a little bit better.
I don't think in this thread there is an argument about deprecating static mut
in some fashion. There is a question of
- how hard should it be deprecated (warnings, suppressible errors, edition based unsuppressible errors)
- what is the implementation burden of each of these options?
- what is the maintenance burden of each of these options?
It seems to me that edition based erroring is a much higher implementation/maintenance burden, and thus is not justified. It also seems that if the path of stabilizing SyncUnsafeCell
and adding a warning/error (to all editions!) is not providing enough oomph, then it is easy to incrementally ratchet that pressure.
I understand that for feature parity the replacements need to be dangerous; what I meant by dangerous is that it's easy to accidentally use it wrong as you get direct access to the value instead of it being wrapped.
You're right about the implementation cost, but what is the maintenance cost for this? Retroactively adding lints to all editions seems like a good idea to me, but I'm not sure how you'd like to "ratchet that pressure". Pressure on what? Increase it by doing what?
For instance, if any use of static mut
warns, it is okay to convert that into an error for a later version/edition of the compiler. This would be an example of "ratcheting".
Ok, I understand now that you're suggesting adding a warn-by-default lint up to 2024/2027, with a deny lint after that, is that right? I don't mind that idea too much, but I still think it's a relatively weak stance on the "issue".
No, I'm not advocating for anything specific. This is a "Pre-RFC", per the title. An RFC doesn't need to have an exhaustive discussion and understanding of alternatives, but it should be able to. What are the courses of action and why is this the best one? This includes some implementation details. Among your proposals here is to add a fairly novel edition based exclusion mechanism, that cost needs to be justified.
Given: The compiler must continue to support static mut
as is, because it must continue to compile old editions. [1]
Opinion: If someone wants to put #![allow(youre_probably_using_static_mut_wrong)]
at the top of their program, that's fine. A warn/deny by default lint is sufficient warning/education in 99% of cases, the other 1% will find other ways to write broken code anyway. [2]
Ok, I understand.
I think we all understand that it's a bit hard to justify the cost here because putting an alternative and yelling at people is comparatively easy and effective. All I can really tell you is that while we obviously need a consensus to do anything I think it aligns with the principles of Rust of safety and all that to get rid of it. From what I heard it's a feature that was put in without any aliasing concerns and it somewhat quickly became clear that aliasing could not be ignored. I don't think such a thing is something anyone wants sitting in Rust indefinitely, if not in 2027, then when? I don't think we're gonna increment the major version, and editions are supposed to be the opportunity for big changes. Why have a feature that will be looked at as "Oh yeah, that's the legacy thing. It's still around for some reason, just don't use it"?
I get that you do have to use static mut
from older crates if their API for some reason does involve using it, it's still just as unsafe etc. but I don't think that invalidates removing adding new static mut
and, in the process, making the language that much leaner and less legacy-baggagey from a user perspective. As we know, there's no precedent for this in Rust, generally editions have been mostly additive[1], but I think that's fine and not of great influence here: Rust is still a relatively new language, and before fairly recently there was a lot of experimentation and not as much in the domain of heavy-weight long-term stuff being put on the table. Now, we do have heavy-weight long-term planning and provisions (t-opsem, the spec, etc.)
In response to your opinion, a lot of people (including me somewhat, though I'm open to change my mind) believe that perma-allows for opinionated lints have no place in the compiler and instead belong in clippy or something else. Permanently deprecating something with no intention of removing it creates one such "opinionated" lint that will just sit there yelling at people who encounter it and those who have actual use for it will have to acknowledge it. I originally did not think of adding a lint for some reason, and someone told me that it should be deprecated first before removal; the lint serves to raise awareness and motivate people to switch off with clear info on when their code should be migrated, not to be a nuisance.
Depending on if you look at keywords being added as a parser addition or valid identifiers being removed ↩︎
Forgive my potential ignorance ... is there any FFI related use case where a static mut
would be required, that could not be satisfied by an interior mutability construct? Perhaps some API where you need to pass in a mutable static variable?
Pretty much no, C can't differentiate at all. Rust code can differentiate and that's where the potential problem is, but I do question what code uses pub static mut
as an actual part of its API surface. Anyone who does use pub static mut
in this way is given sufficient time to migrate, it does force a semver breakage to change but the change is optional as they can wait on the old edition until they feel ready to begin migration.
It's probably not going to be easy, but we can potentially cargo fix
a lot of this stuff away, it won't fix UB if you have it, but it'll compile after then at least and you can use the benefits of SyncUnsafeCell
to better audit your code and see if there are any problematic parts in it.
I just had an idea. Editions are not supposed to delete features, but they can change syntax; suppose we changed the syntax from
static mut FOO: String = String::new();
to
#[legacy_mutable_static]
static FOO: String = String::new();
Then, when someone tries writing static mut
, they can justifiably get not just a deprecation warning but an explanatory syntax error, like
error: static items may not be mutable
help: use a container type with interior mutability, such as `std::sync::Mutex<String>`
and info on using the legacy attribute can be buried inside the error documentation page. This way, when newcomers try to follow the Rust principles they've already learned ("to make something mutable, add mut
") they are guided to appropriate alternatives (whereas “deprecated” sounds both insufficiently scary and too temporary) and existing code continues to be upgradable to new editions.
(Of course, we could also decide that the deprecation is a special deny-by-default lint with the above text. But framing it as a syntactic difference and not just "the combination of static
and mut
is deprecated" seems potentially cleaner.)
This is a cool implementation and I can get behind it, but where did you reach the first assumption that editions aren't supposed to delete features? In my (admittedly far from extensive) search prior to writing this I hadn't found anything that says editions shouldn't remove features.
I don't see why we cannot make changes in new editions that would require crates to make breaking changes if they move to newer editions. If they cannot make breaking changes (which they actually can make much easier compared to rustc or standard library), they can keep using previous edition because we guarantee that rustc would compile all editions of Rust.
Also, I think exposing static mut VAR
is a bad API anyway and nobody should use it anyway, and I am pretty sure that any important project already doesn't do that.
IMHO, even if we don't remove static mut
, we should at least make it deny
by default for both declaring and accessing them so people who want to use that can just allow
it when needed.