Our goal is to make it easy for crates to upgrade to a new edition. When we release a new edition, we also provide tooling to automate the migration. It makes minor changes to your code necessary to make it compatible with the new edition. For example, when migrating to Rust 2018, it changes anything named async to use the equivalent raw identifier syntax: r#async.
The automated migrations are not necessarily perfect: there might be some corner cases where manual changes are still required. The tooling tries hard to avoid changes to semantics that could affect the correctness or performance of the code.
Now, I can't actually find a citation for âit must be possible to upgrade any code to the latest editionâ. But I think it's more of an implicit assumption â and, if it were not the case, that would create an undesirable split where some code can't use anything new.
But even if that's not actually mandatory â please consider my proposal in the frame of âminimizing reasons to objectâ.
In old editions, static mut is treated as an alias for the equivalent SyncUnsafeCell. Taking a reference is treated exactly as if it was &*SyncUnsafeCell::get() or &mut *SyncUnsafeCell::get(). If I understand correctly, this wouldn't change the ABI because SyncUnsafeCell and static mut are bitwise identical.
Because the two syntaxes have the same meaning, old edition crates can change their usages of static mut to SyncUnsafeCell at their own pace.
In new editions, the static mut syntax is removed (optionally being deprecated first)
When a new edition crate depends on an old edition crate, any use of static mut in the old crate is translated to the equivalent SyncUnsafeCell. In new edition crates, only SyncUnsafeCell syntax can be used.
This is similar to Sahl's idea, but comes at it from the other end
A quick search on GitHub shows 15.6k files with pub static mut in them, but even a basic visual scan shows that nearly all of these are C FFI and not for use when imported as a crate, and even things marked pub static mut outside FFI contexts are not necessarily reachable in the module tree from an external crate. My prediction is that the actual breakage is going to be relatively small.
I'm pretty opposed to the 2nd idea; this "I acknowledge that I'm using static mut" type of lint seems annoying to me and not compiler appropriate and I question how far it'll get us in terms of deterrence. It's also faced backlash earlier for other things, this is unique however because it's not a case of "this is probably not what you want to do" but a case of "yes, this is what you want to do, but you're using the wrong tool for it".
We want users to always stay on the latest edition, with latest ergonomic features and semantics. The support for all old editions exists so that existing code can still be used after upgrading the compiler, otherwise the crate ecosystem would be reset by every breaking change. It's not a reason to pin crates to outdated editions, forcing users to forego language improvements and to keep multiple incompatible language dialects in their heads.
That's just your opinion. Public mutable statics are bog standard API in C world, and anything which interacts with C or hardware is likely to use them in some form.
Using a static mut already requires unsafe code. Nobody uses it by accident. Seems like a self-righteous opinion: yes, you need to use this feature, but you are bad, and we'll make sure that you feel bad. Here is an extra lint which you need to dump at the beginning of your crate, or we'll nag you to death for using supported language features.
The alternative of SyncUnsafeCell is just as unsafe as static mut, with the same pitfalls. This whole discussion feels more like people trying to enforce their own aesthetic preferences than a real technical improvement.
As it's currently implemented, static mut has more gotchas than just plain public variables in, say, C, due to aliasing concerns. The aliasing concerns are potentially being moved with #114447 from where pointers are obtained to where they're casted to references. From that point onwards, static mut becomes a legacy, more verbose, less auditable version of SyncUnsafeCell code; SyncUnsafeCell's get method obtains a raw pointer, the only way to directly get a mutable reference is if it's in a mutable place.
I don't think anybody uses it by accident but I can tell you that there is a significant number of people who run into it and think it's what they want when it definitely isn't; static mut is a UB riddled trap, SyncUnsafeCell is only marginally better from a safety standpoint but it's incredibly difficult to stumble upon randomly when trying to port some code. I think it's an unnecessary thing to keep when there's equivalent functionality that doesn't trap people and a good way to make people rethink using globals in such a way.
Well, they would be able to move to it. It is not that big change to do anyway.
And we should do accept breaking some code during edition changes or language would accumulate mistakes like C++ did.
Whole point of Rust is to do things better than C; and globals in C is safer than Rust anyway because C pointers doesn't have all that implicit guarantees that Rust references have.
And Rust would still be able to interact with C and hardware by using interior mutability because of same ABI guarantee.
It is not a true. Accessing global using pointers is safer because it allows user to skip creation of references and use ptr::read and ptr::write calls.
It is the same story as with mem::uninit but now we are actually able to remove the feature at least in newer revisions.
SyncUnsafeCell has literally the same pitfalls. The only difference is that it's slightly more difficult to create a &mut T, but if someone doesn't understand the aliasing issues they're gonna do it anyway. The rule for working with unsafe code containing potential aliasing hazards is always the same: work only with raw pointers. Don't create references, unless you have enforced proper synchronization. Don't assign through pointers, use ptr::write and ptr::read. In the past it wasn't possible to create a pointer to static mut without going through references, but now that we have ptr::addr_of! and ptr::addr_of_mut! there is no longer a reason to prefer a wrapper over directly working with static mut. If anything, the impl Sync for SyncUnsafeCell<T> is more confusing, because one confuse it with the ability to share &T, while nothing like that is guaranteed. With static mut it's more clear that you cannot assume anything about concurrent accesses unless you can prove it.
Using addr_of!/addr_of_mut! is unsafe with static mut while SyncUnsafeCell::get is safe as obtaining a raw pointer to the underlying data should be. Could you explain how you think the Sync implementation for SyncUnsafeCell is confusing (note it's also bounded on T: Sync)? I don't follow. Also, I don't think static mut means anything in the ways of safety to someone not experienced with Rust and who doesn't understand the implications of the borrowing rules and unsynchronised accesses.
Plus, I think it's very unlikely that a beginner will stumble upon SyncUnsafeCell and immediately start to use it and try their hardest to try to get the code to break the aliasing rules, it's just not something that most would look for or would be the first solution to encountering the need to write global mutable state.
While it is currently, it has no strict requirement to be, for the same reason UnsafeCell::get is safe. The currently open PR is making taking references to static mut disallowed (including via method syntax), with an intent to later make addr_of[_mut]! safe.
I tend to agree that it's at least 90% just aesthetics claiming to be improvement, outside of "deliberately making it worse so that you might go static mut->static Mutex/etc instead of ->static SyncUnsafeCell". The explicit &mut STATIC case in particular seems exactly as likely to be written even if it was &mut (*STATIC.get()).
The case where it does seem more likely to make you think is if you have STATIC.foo() and it's easy to forget about the reference. Even then (*STATIC.get()).foo() doesn't require anything explicit about the reference creation, but it's at least got a dereference in there.
The fact that you can't addr_of!()/addr_of_mut!() a static mut is just silly of course.
I guess you guys have a point on the aesthetic part, but, again, one of the main issues is the high discoverability of static mut, I think it's much easier for beginners to find and convince themselves of using it because:
They don't know how rare unsafe code actually is and the ratio of safe to unsafe in Rust.
It's similar to the mutable globals they already know and love, and those never caused them any issues.
They have the preconceived notion that eventually everyone/every project has to get into unsafe code at some point.
They misunderstand Rust's safety guarantees despite the unsafe block in use.
Making static mut behave like SyncUnsafeCell but perma-deprecating it is sort of fine, I'm not a big fan of the idea because it creates equivalent functionality, but if it gets Rust as a whole further away from bad bugs I'm willing to compromise.
I do implore you to look at @kpreid's idea here as it doesn't completely break compatibility.
Iâm just noticing: static mut foo: Foo = âŚ; does not require Foo: Sync, while static foo: SyncUnsafeCell<Foo> = SyncUnsafeCell::new(âŚ); does require Foo: Sync.
#![feature(sync_unsafe_cell)]
use core::cell::SyncUnsafeCell;
static mut P1: *const () = std::ptr::null();
// OKAY
static P2: SyncUnsafeCell<*const ()> = SyncUnsafeCell::new(std::ptr::null());
// ERROR - `*const ()` cannot be shared between threads safely
Is this something where SyncUnsafeCell needs to change anyways? After all, SyncUnsafeCell needs additional synchronization anyways, and that additional synchronization might as well be e.g. mutually exclusive in which case youâd want to require only T: Send, for example. Or maybe many use-case of SyncUnsafeCell would want the Sync after all? (I believe SyncUnsafeCell is probably not intended as a mostly-static mut-replacement, anyways, right? Either way, Iâm not sure off the top of my head, what the intended use-cases of it are, anyways.)
Isn't it? That's the first of two things given in the rationale of the PR that created it. My understanding is that the entire point of SyncUnsafeCell is to have good defaults as a static mut replacement for ad-hoc usage. If you aren't doing something that's one-off, associated with a singlestatic item, then you are better off writing a newtype around UnsafeCell which has whatever unsafe impl Sync bounds suit your use case (though the linked PR disagrees).
Alright, that was just a guess on my end, since I sometimes feel like itâs more convenient to express your custom struct bounds via PhantomData. E.g. if I were to re-implement a custom Mutex implementation with SyncUnsafeCell available, itâd be â perhaps â a reasonable approach to use a SyncUnsafeCell<T> together with PhantomData<Mutex<T>> to just âinheritâ the existing auto traits from Mutex.
(Clicking the link nowâŚ)
Oh, well then⌠implementations of synchronization primitives is listed as the other intended use-case.
In any case, for the concrete use case of implementing a Mutex, that doesnât even work because the implicit T: Sync requirement for SyncUnsafeCell<T>: Sync is overly restrictive there, so that makes two major indented use-cases that might be better of without that.
I wonder if the same isnât commonly enough useful for raw pointer types, too. I commonly feel like a *const T (or NonNull<T>) pointer that just isnât a &Foo for some minor aspect of safety like being unaligned, for instance, is hard to work with in a struct because youâll have to re-write all the Send and Sync implementations carefully, instead of simply using a PhantomData<&'a T> and getting the right implementation from that.
Makes me wonder whether we donât want a more generally useful solution, such as an API as follows, where AssertThreadSafe<UnsafeCell<T>> replaces SyncUnsafeCell<T> (and comes with the more relaxed implementation, too, though the below construction would actually even support either, anyways:
use core::ptr::NonNull;
use core::cell::UnsafeCell;
/// Trait for types that (generally) don't implement Send,
/// but do so only as a precaution. The standard library types `*const T`
/// and `*mut T` are typical examples.
///
/// The types `*const T` and `*mut T` can soundly implement `Send`, even though they don't enforce
/// thread safety, because all API usage that could result in producing data races when the type
/// is sent between threads is already `unsafe`, anyway.
///
/// # Safety
/// This trait may only be implemented for types that can soundly implement `Send`.
/// This is also allowed to be implemented for types that *already do* implement `Send`.
pub unsafe trait SendIsNotUnsound {}
unsafe impl<T> SendIsNotUnsound for *const T {}
unsafe impl<T> SendIsNotUnsound for *mut T {}
unsafe impl<T> SendIsNotUnsound for NonNull<T> {}
unsafe impl<T> SendIsNotUnsound for Option<NonNull<T>> {}
// UnsafeCell has safe API that makes it usable by mutable reference or owned
// access; so the `T: Send` bound is still necessary, and `AssertThreadSafe<UnsafeCell<T>>`
// only lifts the restrictions on the `Sync` implementation.
unsafe impl<T: Send> SendIsNotUnsound for UnsafeCell<T> {}
/// Trait for types that (generally) don't implement Sync,
/// but do so only as a precaution. The standard library types `*const T`,
/// `*mut T`, and `UnsafeCell<T>` are typical examples.
///
/// The types `*const T`, `*mut T`, and `UnsafeCell<T>` can soundly implement `Sync`, even though they don't enforce
/// thread safety, because all API usage that could result in producing data races when the type
/// is shared immutably between threads is already `unsafe`, anyway.
///
/// # Safety
/// This trait may only be implemented for types that can soundly implement `Sync`.
/// This is also allowed to be implemented for types that *already do* implement `Sync`.
pub unsafe trait SyncIsNotUnsound {}
unsafe impl<T> SyncIsNotUnsound for *const T {}
unsafe impl<T> SyncIsNotUnsound for *mut T {}
unsafe impl<T> SyncIsNotUnsound for NonNull<T> {}
unsafe impl<T> SyncIsNotUnsound for Option<NonNull<T>> {}
unsafe impl<T> SyncIsNotUnsound for UnsafeCell<T> {}
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct AssertThreadSafe<T>(pub T);
unsafe impl<T: SendIsNotUnsound> Send for AssertThreadSafe<T> {}
unsafe impl<T: SyncIsNotUnsound> Sync for AssertThreadSafe<T> {}
Well, they agreed at some point about making it constrained on T: Sync, separating the concept of a SyncUnsafeCell and an AlwaysSync or RacyCell. You can find this in #95439. I'm not sure what their idea was but I guess out of the context of this pre-RFC it could have easily made a lot of sense.