From 819f35521c9178d8f876e2090fc933e4b23b710f Mon Sep 17 00:00:00 2001 From: dyslexicsteak <49301588+dyslexicsteak@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:52:17 +0300 Subject: [PATCH 1/7] Create 0000-deprecate-then-remove-static-mut.md --- text/0000-deprecate-then-remove-static-mut.md | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 text/0000-deprecate-then-remove-static-mut.md diff --git a/text/0000-deprecate-then-remove-static-mut.md b/text/0000-deprecate-then-remove-static-mut.md new file mode 100644 index 00000000000..ffe47280ab8 --- /dev/null +++ b/text/0000-deprecate-then-remove-static-mut.md @@ -0,0 +1,177 @@ +# Summary +[summary]: #summary + +Deprecate usage of `static mut` for the 2024 edition of Rust, directing users to switch to interior mutability with subsequent removal of the syntax entirely in the 2027 edition. (This is not pertinent to `&'static mut`) + +# Motivation +[motivation]: #motivation + +The existing `static mut` feature is difficult to use correctly (it's trivial to obtain aliasing exclusive references or encounter UB due to unsynchronised accesses to variables declared with `static mut`) and is becoming redundant due to the expansion of the interior mutability ecosystem which easily replaces `static mut`'s functionality. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +`static mut` is meant to provide statics that can be modified after their initial value is set; variables declared with `static mut` can prove quite problematic when used, however: +```rust +static mut X: i32 = 0; + +fn main() { + let a = unsafe { &mut X }; + let b = unsafe { &mut X }; + + println!("{a} {b}"); +} +``` +Recall Rust's borrowing rules: +- At any given time, you can have either one mutable reference or any number of +immutable references to a place. +- References must always be valid. + +The first rule is violated as we have 2 exclusive (mutable) references to the same datum at the same time and are actively using them in an entirely overlapping fashion. This violation means that our code's behaviour is undefined, and the optimiser is free to do with it as it wishes, potentially breaking it. The code is not guaranteed to print "0 0" and may fail to do so under some circumstances. + +`static mut` also allows for unsynchronised accesses across multiple threads which +can cause data races which are also undefined behaviour. +```rust +use std::thread::spawn; + +static mut X: usize = 0; + +const N: usize = 16; + +fn main() { + let mut thread_pool = Vec::with_capacity(N); + + for i in 0..N { + thread_pool.push(spawn(move || { + unsafe { + X = i; + } + println!("{}", unsafe { FOO }); + })); + } + + for thread in thread_pool { + thread.join().unwrap(); + } +} +``` + +Here, since the usize is not an atomic (with predictable and defined relative ordering) nor synchronised with a `Mutex` or `RwLock` a data race takes place, printing numbers between 0 and 16 in a vaguely increasing fashion. This is undefined behaviour and means that our code is not correct. This and the previous example show UB that is almost trivial to cause which makes it prone to occur by accident in a large codebase. + +Let's try to use `static mut` for FFI purposes (a common application of it), this is usually achieved in this fashion: + +```rust +// using a symbol exported by C code +extern "C" { static mut _c_symbol: Ty; } + +// exporting a symbol from rust code for use by C code +#[no_mangle] +pub static mut _rust_symbol: Ty = val; +``` +This puts our code at risk of causing UB on access as we saw before. Accesses to `static mut` can become difficult to track and reason about very quickly as the size of the codebase increases. As such, by the 2024 edition, we get a deprecation warning (or even deny-by-default lint): +```rust +// WARNING: `static mut` syntax is deprecated as of edition 2024 and is slated +// for removal in edition 2027. Consider using std::cell::SyncUnsafeCell instead. +// Read more at (somewhere, maybe rust blog post). +// Note/fix: +// - extern "C" { static mut _c_symbol: Ty; } +// + extern "C" { static _c_symbol: std::cell::SyncUnsafeCell; } +extern "C" { static mut _c_symbol: Ty; } + +// WARNING: `static mut` syntax is deprecated as of edition 2024, and is slated +// for removal in edition 2027. Consider using std::cell::SyncUnsafeCell instead. Read +// more at (somewhere, maybe rust blog post). +// Note/fix: +// - pub static mut _rust_symbol: Ty = val; +// + pub static _rust_symbol: std::cell::SyncUnsafeCell = std::cell:SyncUnsafeCell::new(val); +#[no_mangle] +pub static mut _rust_symbol: Ty = val; +``` +If we try to do the same thing in the 2027 edition, we get a hard syntax error for not migrating: +```rust +// ERROR: expected one of `:`, `;`, or `=`, found `mut` +// ERROR: error: missing type for `static` item +// Note: +// `static mut` syntax has been removed as of edition 2027: for equivalent behaviour, use std::cell::SyncUnsafeCell instead. +// Fix: +// - extern "C" { static mut _c_symbol: Ty; } +// + extern "C" { static _c_symbol: std::cell::SyncUnsafeCell; } +extern "C" { static mut _c_symbol: Ty; } + +// ERROR: expected one of `:`, `;`, or `=`, found `mut` +// ERROR: error: missing type for `static` item +// Note: +// `static mut` syntax has been removed as of edition 2027: for equivalent behaviour, use std::cell::SyncUnsafeCell instead. +// Fix: +// - pub static mut _rust_symbol: Ty = val; +// + pub static _rust_symbol: std::cell::SyncUnsafeCell = std::cell:SyncUnsafeCell::new(val); +#[no_mangle] +pub static mut _rust_symbol: Ty = val; +``` +Migration from `static mut` in favor of `SyncUnsafeCell` makes code easier to audit, as some operations previously unsafe to perform on `static mut` (such as obtaining a raw pointer to the static) become safe, shifting focus fully to the areas where problems might arise (where the raw pointers are dereferenced) as it is at those points where we create references from raw pointers or use the raw pointers to access the underlying data. Keep in mind, however, that while `SyncUnsafeCell `is a less obvious type/technique to find (harder for beginners to fall into using) and a more verbose one to use, it is still highly unsafe and still does allow someone determined to create aliasing exclusive references to a place; caution should be taken by users of `SyncUnsafeCell` and `UnsafeCell` in general. + +If we follow the diagnostics given by the compiler, we can migrate our code to a safer version of itself and make it easier to audit for any mistakes by better isolating where they can occur. The use of intermediate raw pointers to obtain references also produces marginally better output from the [Miri tool](https://github.com/rust-lang/miri) which allows for better automated detection of problems in the code. + +Things to (please) note: +- Interior mutability types ***are*** present in `core`. This change does not irreversibly break `no_std` code; `std` reexports the types from `core`. +- `std::cell::SyncUnsafeCell` ***is*** the standard type that behaves most equivalently to `static mut`, but it is not necessarily the type you want: + - `std::cell::SyncUnsafeCell` ***is*** `repr(transparent)`, which means that it has the same layout as the wrapped type within, this is the main reason the type exists. + - `std::cell::SyncUnsafeCell` ***does not*** have any more overhead than `std::cell::UnsafeCell`, which is nominally zero. Тhis also means that it has no greater overhead than `static mut` by extension. + - `std::cell::SyncUnsafeCell` ***is*** bound on `T: Sync`, but creating a custom type can alleviate any problems with needing to put `!Sync` types in a `static`. One can make it into `std` (through `core`) at some point. + - `std::cell::SyncUnsafeCell` (like `std::cell::UnsafeCell`) ***does not*** provide any runtime safety, which one should opt for whenever possible. +- `static mut` ***is not*** the only way to initialise globals; `std::sync::LazyLock` (unstable) and `std::sync::OnceLock` provide initialisation on first access and initialisation once, respectively. +- Unsafe code ***should***(probably) be avoided or at least put you on guard. +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation +A lint can be declared for 2024 with a `FutureIncompatibilityReason` of `EditionError` triggered upon detection of a declaration in HIR or the AST. For 2027 a check can be added to the `Parser` struct in the `rustc_parse` crate. + +There is little to no use of `static mut` in the compiler, it is mostly present in `std` and in the implementation of the runtime and in a fashion similar to the above code examples; the few points at which `static mut` is used can be migrated to `SyncUnsafeCell` without causing too much ado. + +A quick code search seemingly shows thousands of usages of `pub static mut`, and, at first glance, this gives the impression of mass breakage, but it is important to consider the following: +1. Whether or not the declared variable is actually reachable from the crate root. +2. Whether or not it is actual API. (Using mutable statics as API surface is present in C, but likely is highly discouraged and rare in Rust). + +In any case, use of old edition `static mut` variables can be allowed for backwards compatibility. +# Drawbacks +[drawbacks]: #drawbacks +Verbosity increases slightly as values need to be wrapped in order to be placed in statics and methods need to be used to get at the underlying data. + +There is a hurdle to migrating to the 2027 edition (deny-by-default lints apparently don't count as breakage, so 2024 is free to have a warn-by-default or deny-by-default lint for deprecation), `cargo fix` can potentially perform some fixes, but the effort of implementing `cargo fix` for this has not yet been gauged. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives +- Do nothing. +- Deprecate `static mut` but don't remove it. +- Replace `static mut` declarations with a `legacy_mutable_static` attribute on `static` declarations. + +Doing nothing would result in a redundant feature that serves no purpose other than being a potential trap for users. + +Deprecation without removal could be done, but after the presumed migration of at least a majority of the ecosystem after the deprecation lint there is no reason to keep the feature in the language. + +Adding an attribute is a decent addition, as it allows people to continue to declare `static mut` variables for any reason (though none come to mind) instead of removing it outright with no way to get at the old feature, but it is still redundant. +# Prior art +[prior-art]: #prior-art +## Work/discussion directly relevant +- Consider deprecation of UB-happy static mut at [#53649](https://github.com/rust-lang/rust/issues/53639) +- Disallow *references* to `static mut` [Edition Idea] at [#114447](https://github.com/rust-lang/rust/issues/114447) +- Deprecate static mut (in the next edition?) on [IRLO](https://internals.rust-lang.org/t/deprecate-static-mut-in-the-next-edition/19975/12) + +## Notable, not directly relevant +- `SyncUnsafeCell` at [#95439](https://github.com/rust-lang/rust/issues/95439) +- `LazyCell/Lock` at [#109736](https://github.com/rust-lang/rust/issues/109736) +- `OnceCell/Lock` at [#74465](https://github.com/rust-lang/rust/issues/74465) +- `AssertThreadSafe`at [T-libs#322](https://github.com/rust-lang/libs-team/issues/322) (IMPORTANT) + +Many have tried to remove/deprecate `static mut` before, the feature is now sufficiently redundant and +subject to replacement to put forth a plan for its eventual removal. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- Should the deprecation lint be warn-by-default or deny-by-default? +- Should we have `cargo fix` support for migration? It's technically possible, but reasonably high effort. +- Should an attribute be included to allow declaring new `static mut` variables even after the feature is removed? + +# Future possibilities +[future-possibilities]: #future-possibilities +I can't think of any besides what was mentioned as of yet. From 55008a486f81f726eacbb7916d6237284a7e6818 Mon Sep 17 00:00:00 2001 From: dyslexicsteak <49301588+dyslexicsteak@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:55:27 +0300 Subject: [PATCH 2/7] Add RFC header. --- text/0000-deprecate-then-remove-static-mut.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/text/0000-deprecate-then-remove-static-mut.md b/text/0000-deprecate-then-remove-static-mut.md index ffe47280ab8..ed086026e5a 100644 --- a/text/0000-deprecate-then-remove-static-mut.md +++ b/text/0000-deprecate-then-remove-static-mut.md @@ -1,3 +1,8 @@ +- Feature `static_mut_2024` +- Start Date: 2024-01-26 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + # Summary [summary]: #summary @@ -23,8 +28,7 @@ fn main() { } ``` Recall Rust's borrowing rules: -- At any given time, you can have either one mutable reference or any number of -immutable references to a place. +- At any given time, you can have either one mutable reference or any number of immutable references to a place. - References must always be valid. The first rule is violated as we have 2 exclusive (mutable) references to the same datum at the same time and are actively using them in an entirely overlapping fashion. This violation means that our code's behaviour is undefined, and the optimiser is free to do with it as it wishes, potentially breaking it. The code is not guaranteed to print "0 0" and may fail to do so under some circumstances. From 06f1ad4936ffe56ebdb559b772f9389c8140e3d5 Mon Sep 17 00:00:00 2001 From: dyslexicsteak <49301588+dyslexicsteak@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:00:42 +0300 Subject: [PATCH 3/7] Rename 0000-deprecate-then-remove-static-mut.md to 3560-deprecate-then-remove-static-mut.md --- ...ove-static-mut.md => 3560-deprecate-then-remove-static-mut.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-deprecate-then-remove-static-mut.md => 3560-deprecate-then-remove-static-mut.md} (100%) diff --git a/text/0000-deprecate-then-remove-static-mut.md b/text/3560-deprecate-then-remove-static-mut.md similarity index 100% rename from text/0000-deprecate-then-remove-static-mut.md rename to text/3560-deprecate-then-remove-static-mut.md From b22ce7b861992de3cf035e732ad442b08e3365b8 Mon Sep 17 00:00:00 2001 From: dyslexicsteak <49301588+dyslexicsteak@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:28:56 +0300 Subject: [PATCH 4/7] Add PR link --- text/3560-deprecate-then-remove-static-mut.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3560-deprecate-then-remove-static-mut.md b/text/3560-deprecate-then-remove-static-mut.md index ed086026e5a..29f04e747a5 100644 --- a/text/3560-deprecate-then-remove-static-mut.md +++ b/text/3560-deprecate-then-remove-static-mut.md @@ -1,6 +1,6 @@ - Feature `static_mut_2024` - Start Date: 2024-01-26 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3560](https://github.com/rust-lang/rfcs/pull/3560) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary From 7748f486c3b5a172375f1934093b58539d53b89d Mon Sep 17 00:00:00 2001 From: dyslexicsteak <49301588+dyslexicsteak@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:56:28 +0300 Subject: [PATCH 5/7] Update and rename 3560-deprecate-then-remove-static-mut.md to 3560-deprecate-static-mut.md Reduce RFC scope and change text to reflect that --- text/3560-deprecate-static-mut.md | 189 ++++++++++++++++++ text/3560-deprecate-then-remove-static-mut.md | 181 ----------------- 2 files changed, 189 insertions(+), 181 deletions(-) create mode 100644 text/3560-deprecate-static-mut.md delete mode 100644 text/3560-deprecate-then-remove-static-mut.md diff --git a/text/3560-deprecate-static-mut.md b/text/3560-deprecate-static-mut.md new file mode 100644 index 00000000000..1c2abd99ca2 --- /dev/null +++ b/text/3560-deprecate-static-mut.md @@ -0,0 +1,189 @@ +- Feature `static_mut_2024` +- Start Date: 2024-01-26 +- RFC PR: [rust-lang/rfcs#3560](https://github.com/rust-lang/rfcs/pull/3560) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +Deprecate `static mut` for the 2024 edition of Rust, directing users to switch to interior mutability. (This is not pertinent to `&'static mut`) + +# Motivation +[motivation]: #motivation + +The existing `static mut` feature is difficult to use correctly (it's trivial to obtain aliasing exclusive references or encounter UB due to unsynchronised accesses to variables declared with `static mut`) and is becoming redundant due to the expansion of the interior mutability ecosystem which easily replaces `static mut`'s functionality. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +`static mut` is meant to provide statics that the program can modify after setting their initial value; variables declared with `static mut` can prove quite problematic when used, however: +```rust +static mut X: i32 = 0; + +fn main() { + let a = unsafe { &mut X }; + let b = unsafe { &mut X }; + + println!("{a} {b}"); +} +``` +Recall Rust's borrowing rules: +- At any given time, you can have either one mutable reference or any number of immutable references to a place. +- References must always be valid. + +We simultaneously have two exclusive (mutable) references to the same datum and actively use them in an entirely overlapping fashion, which means we've violated the first rule. This violation means that our code's behaviour is undefined, and the optimiser is free to do with it as it wishes, potentially breaking it. The code is not guaranteed to print "0 0" and may perform something arbitrary. +`static mut` also allows for unsynchronised accesses across multiple threads, which can cause data races, which are also undefined behaviour. +```rust +use std::thread::spawn; + +static mut X: usize = 0; + +const N: usize = 16; + +fn main() { + let mut thread_pool = Vec::with_capacity(N); + + for i in 0..N { + thread_pool.push(spawn(move || { + unsafe { + X = i; + } + println!("{}", unsafe { FOO }); + })); + } + + for thread in thread_pool { + thread.join().unwrap(); + } +} +``` + +Here, since the X is not an atomic (with predictable and defined relative ordering) nor synchronised with a `Mutex` or `RwLock', a data race takes place, printing numbers between 0 and 16 in a vaguely increasing fashion, data races are undefined behaviour and mean that our code is not correct. This and the previous example show that UB is almost trivial to cause with `static mut`, making it prone to occur by accident in a large codebase. + +Let's try to use `static mut` for FFI purposes (a typical application of it); this is usually achieved in this fashion: +```rust +// using a symbol exported by C code +extern "C" { static mut _c_symbol: Ty; } + +// exporting a symbol from rust code for use by C code +#[no_mangle] +pub static mut _rust_symbol: Ty = val; +``` + +The use of `static mut` in this code puts our code at risk of causing UB on access, as we saw before. Accesses to `static mut` can become difficult to track and reason about very quickly as the size of the codebase increases. As such, by the 2024 edition, we get a deprecation warning (or even deny-by-default lint): +```rust +// WARNING: `static mut` syntax is deprecated as of the 2024 edition. Consider using std::cell::SyncUnsafeCell or another interior mutability type instead. +// Read more at (somewhere, maybe rust blog post). +// Note/fix: +// - extern "C" { static mut _c_symbol: Ty; } +// + extern "C" { static _c_symbol: std::cell::SyncUnsafeCell; } +extern "C" { static mut _c_symbol: Ty; } + +// WARNING: `static mut` syntax is deprecated as of the 2024 edition. Consider using std::cell::SyncUnsafeCell or another interior mutability type instead. +// Read more at (somewhere, maybe rust blog post). +// Note/fix: +// - pub static mut _rust_symbol: Ty = val; +// + pub static _rust_symbol: std::cell::SyncUnsafeCell = std::cell:SyncUnsafeCell::new(val); +#[no_mangle] +pub static mut _rust_symbol: Ty = val; +``` + +Migration from `static mut` in favour of `SyncUnsafeCell` or another alternative makes code easier to audit, as some operations previously unsafe to perform on `static mut` (such as obtaining a raw pointer to the static) become safe, shifting focus entirely to the areas where problems might arise (where the raw pointers are dereferenced) as it is at those points where we create references from raw pointers or use the raw pointers to access the underlying data. Keep in mind, however, that while `SyncUnsafeCell `is a less obvious type/technique to find (harder for beginners to fall into using) and a more verbose one to use, it is still highly unsafe and still does allow someone determined to create aliasing exclusive references to a place; caution should be taken by users of `SyncUnsafeCell` and `UnsafeCell` in general. + +If we follow the diagnostics provided by the compiler, we can migrate our code to a safer version of itself and make it easier to audit for any mistakes by better isolating where they can occur. Using intermediate raw pointers to obtain references also produces marginally better output from the [Miri tool](https://github.com/rust-lang/miri), allowing for more effective automated detection of problems in the code. + +In light of [#114447](https://github.com/rust-lang/rust/issues/114447) paving the way to make references to `static mut` disallowed (and obtaining raw pointers potentially even safe) deprecation of the feature may seem like a waste as obtaining an exclusive borrow of a `static _: SyncUnsafeCell` and a `static mut _: T` both become similar processes. +```rust +// Where A is static SyncUnsafeCell and B is a static mut T +let a = unsafe { &mut *A.get() }; +let b = unsafe { &mut *addr_of_mut!(B)}; +``` + +Additionally, some may not be keen on unsafe interior mutability on `static` as a replacement of `static mut`(mutating through a shared reference with runtime checks or programmer responsibility), citing that `mut` on `static mut` declarations should be a sufficient marker that it is the programmer's responsibility to uphold aliasing and validity invariants. + +There does not have to be any inherent danger in `static mut` for it to be deprecated in favour of more friendly and effective methods to create a mutable global. Still, there are issues with `static mut`. The first issue is that it's a beginner trap; there is symmetry in the way that Rust currently lets you declare variables: +```rust +// In terms of *surface-level* semantics + +// Immutable local +let +// Mutable local +let mut + +// Immutable runtime global +static + +// Mutable runtime global +static mut +``` +This symmetry is a common beginner trap, as it's familiar on two levels: +- Rust introduced `let` and `let mut`, so `static` and `static mut` should work similarly, the only change being `'static` lifetimes on the latter two. +- Mutable globals never hurt anybody in the other languages they've used. +At this stage, many beginners also convince themselves they've reached the point where they need to use unsafe and produce questionable code. + +Disallowing references to `static mut` is a good measure and does eliminate the symmetry of the declarations' behaviour, but it does not eliminate the visual symmetry that takes many beginners to `static mut`. + +Additionally, the fully enabled and referenceless forms of `static mut` violate the principle of least surprise. Rust does not formally require the principle of least surprise, but adhering to it significantly improves the beginner experience. Having only one way to do any given thing makes it easy to comply with the principle. + +In light of the previous point, the interior mutability types, in combination with ordinary `static` declarations, are a solid replacement as they offer a great deal of granularity and are a complete toolset concerning control of access to a place; they are not only a comprehensive replacement for `static mut` of all forms but easy to learn and grapple with as they represent applying the same principle with different requirements and invariants. + +It has to be made clear to people who try to use `static mut` through the deprecation message that unsafe interior mutability primitives are just a way to match the behaviour of `static mut` closely and that they are not the ideal solution instead, they should use the type with the most checked invariants, and that applies to their use case. + + +Things to (please) note: +- Interior mutability types ***are*** present in `core`. This change does not irreversibly break `no_std` code; `std` reexports the types from `core`. +- `std::cell::SyncUnsafeCell` ***is*** the standard type that behaves most equivalently to `static mut`, but it is not necessarily the type you want: + - `std::cell::SyncUnsafeCell` ***is*** `repr(transparent)`, which means it has the same layout as the wrapped type within; this is the main reason the type exists. + - `std::cell::SyncUnsafeCell` ***does not*** have any more overhead than `std::cell::UnsafeCell`, which is nominally zero. This also means that it has no greater overhead than `static mut` by extension. + - `std::cell::SyncUnsafeCell` ***is*** bound on `T: Sync`, but creating a custom type can alleviate any problems with needing to put `!Sync` types in a `static`. One can make it into `std` (through `core`) at some point. + - `std::cell::SyncUnsafeCell` (like `std::cell::UnsafeCell`) ***does not*** provide any runtime safety, which one should opt for whenever possible. +- `static mut` ***is not*** the only way to initialise globals; `std::sync::LazyLock` (unstable) and `std::sync::OnceLock` provide initialisation on first access and initialisation once, respectively. +- Unsafe code ***should***(probably) be avoided or at least put you on guard. +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation +A custom lint will have to be introduced under the `deprecated` family to support this. This can be changed later to reflect the stance of the Rust project on `static mut`. + +There is little to no use of `static mut` in the compiler; it is present mainly in `std` and in the implementation of the runtime in a fashion similar to the above code examples; the few points at which `static mut` is used can be migrated to `SyncUnsafeCell` without causing too much ado. + +A quick code search seemingly shows thousands of usages of `pub static mut`, and, at first glance, this gives the impression of mass breakage, but it is crucial to consider the following: +1. Whether or not the declared variable is reachable from the crate root. +2. Whether or not it is actual API. (Using mutable statics as API surface is present in C, but likely is highly discouraged and rare in Rust). + +In any case, the use of old edition `static mut` variables can be allowed for backwards compatibility. +# Drawbacks +[drawbacks]: #drawbacks +Verbosity increases as values must be wrapped in an interior mutability type to be placed in statics if they're to be modified later, and methods must be used to get at the underlying data. + +There is a hurdle to migrating to the 2027 edition (deny-by-default lints don't count as breakage, so 2024 is free to have a warn-by-default or deny-by-default lint for deprecation), `cargo fix` can potentially perform some fixes, but the effort of implementing `cargo fix` for this has not yet been gauged. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives +- Do nothing. + +Doing nothing would result in a redundant, jarring feature that serves no purpose other than being a potential new user trap. + +# Prior art +[prior-art]: #prior-art +## Work/discussion directly relevant +- Consider deprecation of UB-happy static mut at [#53649](https://github.com/rust-lang/rust/issues/53639) +- Disallow *references* to `static mut` [Edition Idea] at [#114447](https://github.com/rust-lang/rust/issues/114447) +- Deprecate static mut (in the next edition?) on [IRLO](https://internals.rust-lang.org/t/deprecate-static-mut-in-the-next-edition/19975/12) + +## Notable, not directly relevant +- `SyncUnsafeCell` at [#95439](https://github.com/rust-lang/rust/issues/95439) +- `LazyCell/Lock` at [#109736](https://github.com/rust-lang/rust/issues/109736) +- `OnceCell/Lock` at [#74465](https://github.com/rust-lang/rust/issues/74465) +- `AssertThreadSafe`at [T-libs#322](https://github.com/rust-lang/libs-team/issues/322) (IMPORTANT) + +Many have tried to remove/deprecate `static mut` before; the feature is now sufficiently redundant and +subject to replacement to put forth a plan for its eventual removal. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- Should the deprecation lint be warn-by-default or deny-by-default? +- Should we have `cargo fix` support for migration? It's technically possible, but reasonably high effort. + +# Future possibilities +[future-possibilities]: #future-possibilities +Removing `static mut` declarations altogether (edition gated) can be done in the future. Using `static mut` from previous editions can be allowed, and a `legacy_static_mut` attribute can be used to allow new declarations to enable code to upgrade without changing the semantics of their declarations if they have a reason not to replace them. diff --git a/text/3560-deprecate-then-remove-static-mut.md b/text/3560-deprecate-then-remove-static-mut.md deleted file mode 100644 index 29f04e747a5..00000000000 --- a/text/3560-deprecate-then-remove-static-mut.md +++ /dev/null @@ -1,181 +0,0 @@ -- Feature `static_mut_2024` -- Start Date: 2024-01-26 -- RFC PR: [rust-lang/rfcs#3560](https://github.com/rust-lang/rfcs/pull/3560) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) - -# Summary -[summary]: #summary - -Deprecate usage of `static mut` for the 2024 edition of Rust, directing users to switch to interior mutability with subsequent removal of the syntax entirely in the 2027 edition. (This is not pertinent to `&'static mut`) - -# Motivation -[motivation]: #motivation - -The existing `static mut` feature is difficult to use correctly (it's trivial to obtain aliasing exclusive references or encounter UB due to unsynchronised accesses to variables declared with `static mut`) and is becoming redundant due to the expansion of the interior mutability ecosystem which easily replaces `static mut`'s functionality. - -# Guide-level explanation -[guide-level-explanation]: #guide-level-explanation - -`static mut` is meant to provide statics that can be modified after their initial value is set; variables declared with `static mut` can prove quite problematic when used, however: -```rust -static mut X: i32 = 0; - -fn main() { - let a = unsafe { &mut X }; - let b = unsafe { &mut X }; - - println!("{a} {b}"); -} -``` -Recall Rust's borrowing rules: -- At any given time, you can have either one mutable reference or any number of immutable references to a place. -- References must always be valid. - -The first rule is violated as we have 2 exclusive (mutable) references to the same datum at the same time and are actively using them in an entirely overlapping fashion. This violation means that our code's behaviour is undefined, and the optimiser is free to do with it as it wishes, potentially breaking it. The code is not guaranteed to print "0 0" and may fail to do so under some circumstances. - -`static mut` also allows for unsynchronised accesses across multiple threads which -can cause data races which are also undefined behaviour. -```rust -use std::thread::spawn; - -static mut X: usize = 0; - -const N: usize = 16; - -fn main() { - let mut thread_pool = Vec::with_capacity(N); - - for i in 0..N { - thread_pool.push(spawn(move || { - unsafe { - X = i; - } - println!("{}", unsafe { FOO }); - })); - } - - for thread in thread_pool { - thread.join().unwrap(); - } -} -``` - -Here, since the usize is not an atomic (with predictable and defined relative ordering) nor synchronised with a `Mutex` or `RwLock` a data race takes place, printing numbers between 0 and 16 in a vaguely increasing fashion. This is undefined behaviour and means that our code is not correct. This and the previous example show UB that is almost trivial to cause which makes it prone to occur by accident in a large codebase. - -Let's try to use `static mut` for FFI purposes (a common application of it), this is usually achieved in this fashion: - -```rust -// using a symbol exported by C code -extern "C" { static mut _c_symbol: Ty; } - -// exporting a symbol from rust code for use by C code -#[no_mangle] -pub static mut _rust_symbol: Ty = val; -``` -This puts our code at risk of causing UB on access as we saw before. Accesses to `static mut` can become difficult to track and reason about very quickly as the size of the codebase increases. As such, by the 2024 edition, we get a deprecation warning (or even deny-by-default lint): -```rust -// WARNING: `static mut` syntax is deprecated as of edition 2024 and is slated -// for removal in edition 2027. Consider using std::cell::SyncUnsafeCell instead. -// Read more at (somewhere, maybe rust blog post). -// Note/fix: -// - extern "C" { static mut _c_symbol: Ty; } -// + extern "C" { static _c_symbol: std::cell::SyncUnsafeCell; } -extern "C" { static mut _c_symbol: Ty; } - -// WARNING: `static mut` syntax is deprecated as of edition 2024, and is slated -// for removal in edition 2027. Consider using std::cell::SyncUnsafeCell instead. Read -// more at (somewhere, maybe rust blog post). -// Note/fix: -// - pub static mut _rust_symbol: Ty = val; -// + pub static _rust_symbol: std::cell::SyncUnsafeCell = std::cell:SyncUnsafeCell::new(val); -#[no_mangle] -pub static mut _rust_symbol: Ty = val; -``` -If we try to do the same thing in the 2027 edition, we get a hard syntax error for not migrating: -```rust -// ERROR: expected one of `:`, `;`, or `=`, found `mut` -// ERROR: error: missing type for `static` item -// Note: -// `static mut` syntax has been removed as of edition 2027: for equivalent behaviour, use std::cell::SyncUnsafeCell instead. -// Fix: -// - extern "C" { static mut _c_symbol: Ty; } -// + extern "C" { static _c_symbol: std::cell::SyncUnsafeCell; } -extern "C" { static mut _c_symbol: Ty; } - -// ERROR: expected one of `:`, `;`, or `=`, found `mut` -// ERROR: error: missing type for `static` item -// Note: -// `static mut` syntax has been removed as of edition 2027: for equivalent behaviour, use std::cell::SyncUnsafeCell instead. -// Fix: -// - pub static mut _rust_symbol: Ty = val; -// + pub static _rust_symbol: std::cell::SyncUnsafeCell = std::cell:SyncUnsafeCell::new(val); -#[no_mangle] -pub static mut _rust_symbol: Ty = val; -``` -Migration from `static mut` in favor of `SyncUnsafeCell` makes code easier to audit, as some operations previously unsafe to perform on `static mut` (such as obtaining a raw pointer to the static) become safe, shifting focus fully to the areas where problems might arise (where the raw pointers are dereferenced) as it is at those points where we create references from raw pointers or use the raw pointers to access the underlying data. Keep in mind, however, that while `SyncUnsafeCell `is a less obvious type/technique to find (harder for beginners to fall into using) and a more verbose one to use, it is still highly unsafe and still does allow someone determined to create aliasing exclusive references to a place; caution should be taken by users of `SyncUnsafeCell` and `UnsafeCell` in general. - -If we follow the diagnostics given by the compiler, we can migrate our code to a safer version of itself and make it easier to audit for any mistakes by better isolating where they can occur. The use of intermediate raw pointers to obtain references also produces marginally better output from the [Miri tool](https://github.com/rust-lang/miri) which allows for better automated detection of problems in the code. - -Things to (please) note: -- Interior mutability types ***are*** present in `core`. This change does not irreversibly break `no_std` code; `std` reexports the types from `core`. -- `std::cell::SyncUnsafeCell` ***is*** the standard type that behaves most equivalently to `static mut`, but it is not necessarily the type you want: - - `std::cell::SyncUnsafeCell` ***is*** `repr(transparent)`, which means that it has the same layout as the wrapped type within, this is the main reason the type exists. - - `std::cell::SyncUnsafeCell` ***does not*** have any more overhead than `std::cell::UnsafeCell`, which is nominally zero. Тhis also means that it has no greater overhead than `static mut` by extension. - - `std::cell::SyncUnsafeCell` ***is*** bound on `T: Sync`, but creating a custom type can alleviate any problems with needing to put `!Sync` types in a `static`. One can make it into `std` (through `core`) at some point. - - `std::cell::SyncUnsafeCell` (like `std::cell::UnsafeCell`) ***does not*** provide any runtime safety, which one should opt for whenever possible. -- `static mut` ***is not*** the only way to initialise globals; `std::sync::LazyLock` (unstable) and `std::sync::OnceLock` provide initialisation on first access and initialisation once, respectively. -- Unsafe code ***should***(probably) be avoided or at least put you on guard. -# Reference-level explanation -[reference-level-explanation]: #reference-level-explanation -A lint can be declared for 2024 with a `FutureIncompatibilityReason` of `EditionError` triggered upon detection of a declaration in HIR or the AST. For 2027 a check can be added to the `Parser` struct in the `rustc_parse` crate. - -There is little to no use of `static mut` in the compiler, it is mostly present in `std` and in the implementation of the runtime and in a fashion similar to the above code examples; the few points at which `static mut` is used can be migrated to `SyncUnsafeCell` without causing too much ado. - -A quick code search seemingly shows thousands of usages of `pub static mut`, and, at first glance, this gives the impression of mass breakage, but it is important to consider the following: -1. Whether or not the declared variable is actually reachable from the crate root. -2. Whether or not it is actual API. (Using mutable statics as API surface is present in C, but likely is highly discouraged and rare in Rust). - -In any case, use of old edition `static mut` variables can be allowed for backwards compatibility. -# Drawbacks -[drawbacks]: #drawbacks -Verbosity increases slightly as values need to be wrapped in order to be placed in statics and methods need to be used to get at the underlying data. - -There is a hurdle to migrating to the 2027 edition (deny-by-default lints apparently don't count as breakage, so 2024 is free to have a warn-by-default or deny-by-default lint for deprecation), `cargo fix` can potentially perform some fixes, but the effort of implementing `cargo fix` for this has not yet been gauged. - -# Rationale and alternatives -[rationale-and-alternatives]: #rationale-and-alternatives -- Do nothing. -- Deprecate `static mut` but don't remove it. -- Replace `static mut` declarations with a `legacy_mutable_static` attribute on `static` declarations. - -Doing nothing would result in a redundant feature that serves no purpose other than being a potential trap for users. - -Deprecation without removal could be done, but after the presumed migration of at least a majority of the ecosystem after the deprecation lint there is no reason to keep the feature in the language. - -Adding an attribute is a decent addition, as it allows people to continue to declare `static mut` variables for any reason (though none come to mind) instead of removing it outright with no way to get at the old feature, but it is still redundant. -# Prior art -[prior-art]: #prior-art -## Work/discussion directly relevant -- Consider deprecation of UB-happy static mut at [#53649](https://github.com/rust-lang/rust/issues/53639) -- Disallow *references* to `static mut` [Edition Idea] at [#114447](https://github.com/rust-lang/rust/issues/114447) -- Deprecate static mut (in the next edition?) on [IRLO](https://internals.rust-lang.org/t/deprecate-static-mut-in-the-next-edition/19975/12) - -## Notable, not directly relevant -- `SyncUnsafeCell` at [#95439](https://github.com/rust-lang/rust/issues/95439) -- `LazyCell/Lock` at [#109736](https://github.com/rust-lang/rust/issues/109736) -- `OnceCell/Lock` at [#74465](https://github.com/rust-lang/rust/issues/74465) -- `AssertThreadSafe`at [T-libs#322](https://github.com/rust-lang/libs-team/issues/322) (IMPORTANT) - -Many have tried to remove/deprecate `static mut` before, the feature is now sufficiently redundant and -subject to replacement to put forth a plan for its eventual removal. - -# Unresolved questions -[unresolved-questions]: #unresolved-questions - -- Should the deprecation lint be warn-by-default or deny-by-default? -- Should we have `cargo fix` support for migration? It's technically possible, but reasonably high effort. -- Should an attribute be included to allow declaring new `static mut` variables even after the feature is removed? - -# Future possibilities -[future-possibilities]: #future-possibilities -I can't think of any besides what was mentioned as of yet. From e6bdb74477ba8ed805c1d9fc0235c08212f73184 Mon Sep 17 00:00:00 2001 From: dyslexicsteak <49301588+dyslexicsteak@users.noreply.github.com> Date: Sat, 3 Feb 2024 17:00:43 +0300 Subject: [PATCH 6/7] Fix grammar and correct an incorrect statement --- text/3560-deprecate-static-mut.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/text/3560-deprecate-static-mut.md b/text/3560-deprecate-static-mut.md index 1c2abd99ca2..667d36ed662 100644 --- a/text/3560-deprecate-static-mut.md +++ b/text/3560-deprecate-static-mut.md @@ -127,8 +127,7 @@ Additionally, the fully enabled and referenceless forms of `static mut` violate In light of the previous point, the interior mutability types, in combination with ordinary `static` declarations, are a solid replacement as they offer a great deal of granularity and are a complete toolset concerning control of access to a place; they are not only a comprehensive replacement for `static mut` of all forms but easy to learn and grapple with as they represent applying the same principle with different requirements and invariants. -It has to be made clear to people who try to use `static mut` through the deprecation message that unsafe interior mutability primitives are just a way to match the behaviour of `static mut` closely and that they are not the ideal solution instead, they should use the type with the most checked invariants, and that applies to their use case. - +It has to be made clear to people who try to use `static mut` through the deprecation message that unsafe interior mutability primitives are just a way to match the behaviour of `static mut` closely and that they are not the ideal solution. Instead, they should use the type with the most checked invariants, and that applies to their use case. Things to (please) note: - Interior mutability types ***are*** present in `core`. This change does not irreversibly break `no_std` code; `std` reexports the types from `core`. @@ -137,7 +136,7 @@ Things to (please) note: - `std::cell::SyncUnsafeCell` ***does not*** have any more overhead than `std::cell::UnsafeCell`, which is nominally zero. This also means that it has no greater overhead than `static mut` by extension. - `std::cell::SyncUnsafeCell` ***is*** bound on `T: Sync`, but creating a custom type can alleviate any problems with needing to put `!Sync` types in a `static`. One can make it into `std` (through `core`) at some point. - `std::cell::SyncUnsafeCell` (like `std::cell::UnsafeCell`) ***does not*** provide any runtime safety, which one should opt for whenever possible. -- `static mut` ***is not*** the only way to initialise globals; `std::sync::LazyLock` (unstable) and `std::sync::OnceLock` provide initialisation on first access and initialisation once, respectively. +- `static mut` ***is not*** the only way to initialise globals without a `const` value; `std::sync::LazyLock` (unstable) and `std::sync::OnceLock` provide initialisation on first access and initialisation once, respectively. - Unsafe code ***should***(probably) be avoided or at least put you on guard. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From c1d963f9ecad897cc3269f5e16bc9a8fe54c1190 Mon Sep 17 00:00:00 2001 From: dyslexicsteak <49301588+dyslexicsteak@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:13:03 +0300 Subject: [PATCH 7/7] Update 3560-deprecate-static-mut.md Rectify a quoting error --- text/3560-deprecate-static-mut.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3560-deprecate-static-mut.md b/text/3560-deprecate-static-mut.md index 667d36ed662..2c1d36a49d6 100644 --- a/text/3560-deprecate-static-mut.md +++ b/text/3560-deprecate-static-mut.md @@ -58,7 +58,7 @@ fn main() { } ``` -Here, since the X is not an atomic (with predictable and defined relative ordering) nor synchronised with a `Mutex` or `RwLock', a data race takes place, printing numbers between 0 and 16 in a vaguely increasing fashion, data races are undefined behaviour and mean that our code is not correct. This and the previous example show that UB is almost trivial to cause with `static mut`, making it prone to occur by accident in a large codebase. +Here, since the X is not an atomic (with predictable and defined relative ordering) nor synchronised with a `Mutex` or `RwLock`, a data race takes place, printing numbers between 0 and 16 in a vaguely increasing fashion, data races are undefined behaviour and mean that our code is not correct. This and the previous example show that UB is almost trivial to cause with `static mut`, making it prone to occur by accident in a large codebase. Let's try to use `static mut` for FFI purposes (a typical application of it); this is usually achieved in this fashion: ```rust