-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Add SyncUnsafeCell. #95438
Add SyncUnsafeCell. #95438
Conversation
r? @scottmcm (rust-highfive has picked a reviewer for you, use r? to override) |
65d0df7
to
750ab03
Compare
Bikeshed: I'm a little concerned that the name |
I'm settling in for a long bikeshed. |
Yeah, that's a valid point. There's a few alternative names on the tracking issue that we can bikeshed over. I was thinking of |
I will say that this is a recurring problem with naming, where we have both Sync and non-Sync versions of some type and we need to figure out how to express the difference. Maybe there's a broader solution? |
I also like having I think I like 🤔 |
I think we can probably at least agree that |
I should note that I'm not sending this PR as 'the solution' to #53639. This type stands on its own, and might or might not be part of a proper solution for deprecating |
Can you elaborate on how else one might find this useful? Some examples of suggested use cases would be nice to have in the doc comment. |
I wanted to show someone a very simple example of synchronization through an AtomicBool like this: let value = UnsafeCell::new(0);
let ready = AtomicBool::new(false);
// thread 1:
unsafe { *value.get() = 123 };
ready.store(true, Release);
// thread 2:
if ready.load(Acquire) {
dbg!(unsafe { *value.get() });
} And then realized I couldn't really give that example without defining my own type first, because the only way to express it's okay for my UnsafeCell to be shared between threads is by wrapping it in my own type and adding an More generally, I think it'd be nice to use it in e.g. an |
(Edited to provide full context) I've found myself needing something like In the Atmel SAME70 microcontroller, the hardware uses DMA (direct memory access) in order to send and receive ethernet frames. In order to do that (messy PoC code here), the process looks (roughly) like this:
In this case, the Buffer Descriptors act as the synchronization mechanism between the hardware and software. It is common in embedded rust to consider the hardware as a "separate thread", as it often executes asynchronously from the main software, in this case receiving and sending ethernet frames. Additionally, it is considered "safest" to use static memory buffers when interacting with DMA, as memory on the stack can suffer from the same kind of "liveness" issues as you have with sharing stack data to multiple threads (basically: if you forget to 'turn off' the DMA before leaving the relevant stack frame: oops! memory corruption!). For both the Buffer Descriptors and the Buffers themselves, they need to be Safety is guaranteed by using a certain bit in the buffer descriptor in order to denote "ownership" of a given buffer. For example, when receiving frames:
This is (admittedly) a long-winded example of the same situation/use-case that @m-ou-se described, using an atomicbool/bit to synchronize access, but this is a decently common pattern in the embedded world, and leads to a swath of |
This seems reasonable to me. I do prefer I won't bikeshed Could the compiler potentially see through accesses to this, and treat them as compiler-level non-atomic (such as doing partial/tearable loads) or non-volatile (such as remembering the result of a previous read operation rather than re-reading), such that simple ways of protecting the cell aren't enough? How can someone do the equivalent of std::ptr::read_volatile with a Given some answer to that question (even if the answer is "we don't need to do that, here's why"), I'd be happy to r+ this. |
Whatever you use to provide the synchronization (e.g. a mutex or raw atomics) should provide everything that's needed to make sure things are read/written after/before the right moments. (E.g. memory ordering on atomics.)
An unsafe cell only provides access through .get() which gives a pointer, so read_volatile and friends can direclty be used on that pointer, just like how every other interaction needs to go through that pointer. All the use cases for SyncUnsafeCell just use UnsafeCell today, but have to be paired with a |
@bors r+ |
📌 Commit f225808 has been approved by |
…plett Add SyncUnsafeCell. This adds `SyncUnsafeCell`, which is just `UnsafeCell` except it implements `Sync`. This was first proposed under the name `RacyUnsafeCell` here: rust-lang#53639 (comment) and here: rust-lang#53639 (comment) and here: rust-lang#53639 (comment) It allows you to create an UnsafeCell that is Sync without having to wrap it in a struct first (and then implement Sync for that struct). E.g. `static X: SyncUnsafeCell<i32>`. Using a regular `UnsafeCell` as `static` is not possible, because it isn't `Sync`. We have a language workaround for it called `static mut`, but it's nice to be able to use the proper type for such unsafety instead. It also makes implementing synchronization primitives based on unsafe cells slightly less verbose, because by using `SyncUnsafeCell` for `UnsafeCell`s that are shared between threads, you don't need a separate `impl<..> Sync for ..`. Using this type also clearly documents that the cell is expected to be accessed from multiple threads.
Rollup of 7 pull requests Successful merges: - rust-lang#92942 (stabilize windows_process_extensions_raw_arg) - rust-lang#94817 (Release notes for 1.60.0) - rust-lang#95343 (Reduce unnecessary escaping in proc_macro::Literal::character/string) - rust-lang#95431 (Stabilize total_cmp) - rust-lang#95438 (Add SyncUnsafeCell.) - rust-lang#95467 (Windows: Synchronize asynchronous pipe reads and writes) - rust-lang#95609 (Suggest borrowing when trying to coerce unsized type into `dyn Trait`) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
This adds
SyncUnsafeCell
, which is justUnsafeCell
except it implementsSync
.This was first proposed under the name
RacyUnsafeCell
here: #53639 (comment) and here: #53639 (comment) and here: #53639 (comment)It allows you to create an UnsafeCell that is Sync without having to wrap it in a struct first (and then implement Sync for that struct).
E.g.
static X: SyncUnsafeCell<i32>
. Using a regularUnsafeCell
asstatic
is not possible, because it isn'tSync
. We have a language workaround for it calledstatic mut
, but it's nice to be able to use the proper type for such unsafety instead.It also makes implementing synchronization primitives based on unsafe cells slightly less verbose, because by using
SyncUnsafeCell
forUnsafeCell
s that are shared between threads, you don't need a separateimpl<..> Sync for ..
. Using this type also clearly documents that the cell is expected to be accessed from multiple threads.