ptr_cell/lib.rs
1//! # Simple thread-safe cell
2//!
3//! [`PtrCell`] is an atomic cell type that allows safe, concurrent access to shared data. No
4//! [`std`][1], no data races, no [nasal demons][2] (undefined behavior), and most importantly, no
5//! locks
6//!
7//! This type is only useful in scenarios where you need to update a shared value by moving in and
8//! out of it. If you want to concurrently update a value through mutable references and don't
9//! require support for `no_std`, take a look at the standard [`Mutex`][3] and [`RwLock`][4] instead
10//!
11//! #### Offers:
12//!
13//! - **Familiarity**: `PtrCell`'s API was modelled after `std`'s [Cell](core::cell::Cell)
14//!
15//! - **Easy Concurrency**: No more `Arc<Mutex<T>>`, `Arc::clone()`, and `Mutex::lock().expect()`!
16//! Leave the data static and then point to it when you need to. It's a _single instruction_ on most
17//! modern platforms
18//!
19//! #### Limitations:
20//!
21//! - **Heap Allocation**: Every value you insert into `PtrCell` must first be allocated using
22//! [`Box`]. Allocating on the heap is, computationally, a moderately expensive operation. To
23//! address this, the cell exposes a pointer API that can be used to avoid allocating the same
24//! values multiple times. Future releases will primarily rely on the stack
25//!
26//! ## Usage
27//!
28//! ```rust
29//! use ptr_cell::{PtrCell, Semantics::Relaxed};
30//!
31//! let cell: PtrCell<u16> = 0x81D.into();
32//!
33//! assert_eq!(cell.replace(Some(2047), Relaxed), Some(0x81D));
34//! assert_eq!(cell.is_empty(Relaxed), false);
35//! assert_eq!(cell.take(Relaxed), Some(2047))
36//! ```
37//!
38//! ## Semantics
39//!
40//! [`PtrCell`] allows you to specify memory ordering semantics for its internal atomic operations
41//! through the [`Semantics`] enum. Each variant is different in how it balances synchronization and
42//! performace. Here's a comparison of the available semantics:
43//!
44//! | Variant | Overhead | Synchronization |
45//! |---|---|---|
46//! | [`Relaxed`](Semantics::Relaxed) | Negligible | None |
47//! | [`Coupled`](Semantics::Coupled) | Acceptable | Intuitive |
48//! | [`Ordered`](Semantics::Ordered) | Noticeable | Strict |
49//!
50//! `Coupled` is what you'd typically use. However, other orderings have their use cases too. For
51//! example, the `Relaxed` semantics could be useful when the operations are already synchronized
52//! through other means, like [fences](core::sync::atomic::fence). As always, the documentation for
53//! each item contains more details
54//!
55//! ## Examples
56//!
57//! The code below finds the maximum value of a sequence by concurrently processing its halves.
58//! Notice how the code doesn't read the shared value. Instead, it uses moves and corrects previous
59//! operations as new data comes in
60//!
61//! ```rust
62//! use ptr_cell::{PtrCell, Semantics};
63//! use std::sync::Arc;
64//!
65//! fn main() {
66//! const VALUES: [u8; 11] = [47, 12, 88, 45, 67, 34, 78, 90, 11, 77, 33];
67//!
68//! let cell = PtrCell::default();
69//! let maximum = Arc::new(cell);
70//!
71//! let (left, right) = VALUES.split_at(VALUES.len() / 2);
72//!
73//! let handles = [left, right].map(|half| {
74//! let maximum = Arc::clone(&maximum);
75//!
76//! std::thread::spawn(move || maximize_in(half, &maximum))
77//! });
78//!
79//! for worker in handles {
80//! if let Err(payload) = worker.join() {
81//! std::panic::resume_unwind(payload)
82//! }
83//! }
84//!
85//! assert_eq!(maximum.take(Semantics::Relaxed), Some(90))
86//! }
87//!
88//! fn maximize_in<T>(sequence: &[T], buffer: &PtrCell<T>)
89//! where
90//! T: Ord + Copy,
91//! {
92//! for &item in sequence {
93//! let mut slot = Some(item);
94//!
95//! loop {
96//! let previous = buffer.replace(slot, Semantics::Relaxed);
97//!
98//! match slot < previous {
99//! true => slot = previous,
100//! false => break,
101//! }
102//! }
103//! }
104//! }
105//! ```
106//!
107//! [1]: https://doc.rust-lang.org/std/index.html
108//! [2]: https://groups.google.com/g/comp.std.c/c/ycpVKxTZkgw/m/S2hHdTbv4d8J?hl=en
109//! [3]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
110//! [4]: https://doc.rust-lang.org/std/sync/struct.RwLock.html
111
112#![no_std]
113#![warn(missing_docs, clippy::all, clippy::pedantic, clippy::cargo)]
114#![allow(clippy::must_use_candidate)]
115#![forbid(unsafe_op_in_unsafe_fn)]
116
117extern crate alloc;
118
119use alloc::boxed::Box;
120use core::sync::atomic::Ordering;
121
122// 3.0.0:
123// - Just fix `replace_ptr` already!!! \
124// - Make `Semantics` exhaustive |
125// - Add the default `std` feature /
126// - Figure out how to properly generalize to the stack (see notes below)
127// - Implement `get`, `update`, and some traits by using brief spinlocking
128// - Add "virtually" to "no locks" in the top-level docs (very important)
129// - Add `from_mut` like on std's Cell
130
131// It's possible to ditch heap allocation entirely if we pre-allocate a buffer of type T.
132// Pre-allocating an array of N buffers (const N: usize) could amortize performance losses during
133// periods of high contention
134
135// Top-level:
136//
137// ## Features
138//
139// - **`std`**: Enables everything that may depend on the standard library. Currently, there are no
140// such items. Could optimize performace in future updates
141
142/// Thread-safe cell based on atomic pointers
143///
144/// This type stores its data externally by _leaking_ it with [`Box`]. Synchronization is achieved
145/// by atomically manipulating pointers to the data
146///
147/// # Usage
148///
149/// ```rust
150/// use ptr_cell::{PtrCell, Semantics::Relaxed};
151///
152/// let cell: PtrCell<u16> = 0x81D.into();
153///
154/// assert_eq!(cell.replace(Some(2047), Relaxed), Some(0x81D));
155/// assert_eq!(cell.is_empty(Relaxed), false);
156/// assert_eq!(cell.take(Relaxed), Some(2047))
157/// ```
158///
159/// # Pointer Safety
160///
161/// When dereferencing a pointer to the cell's value, you must ensure that the pointed-to memory
162/// hasn't been [reclaimed](Self::heap_reclaim). For example, [`replace`](Self::replace) and its
163/// derivatives ([`set`](Self::set) and [`take`](Self::take)) automatically reclaim memory. Be
164/// careful not to miss any calls to such functions made from other threads
165///
166/// This also applies to externally-sourced pointers, like the `ptr` parameter in
167/// [`from_ptr`](Self::from_ptr)
168#[repr(transparent)]
169pub struct PtrCell<T> {
170 /// Pointer to the contained value
171 ///
172 /// #### Invariants
173 ///
174 /// - **If non-null**: Must point to memory that conforms to the [memory layout][1] used by
175 /// [`Box`]
176 ///
177 /// [1]: https://doc.rust-lang.org/std/boxed/index.html#memory-layout
178 value: core::sync::atomic::AtomicPtr<T>,
179}
180
181impl<T> PtrCell<T> {
182 /// Inserts the value constructed from this cell by `new` into the cell itself
183 ///
184 /// Think of this like the `push` method of a linked list, where each node contains a `PtrCell`
185 ///
186 /// # Examples
187 ///
188 /// The code below turns a sentence into a naive linked list of words, which is then assembled
189 /// back into a [`String`][1]
190 ///
191 /// ```rust
192 /// use ptr_cell::{PtrCell, Semantics};
193 ///
194 /// struct Node<T> {
195 /// pub value: T,
196 /// pub next: PtrCell<Self>,
197 /// }
198 ///
199 /// impl<T> AsMut<PtrCell<Self>> for Node<T> {
200 /// fn as_mut(&mut self) -> &mut ptr_cell::PtrCell<Self> {
201 /// &mut self.next
202 /// }
203 /// }
204 ///
205 /// let cell = PtrCell::default();
206 ///
207 /// for value in "Hachó en México".split_whitespace().rev() {
208 /// cell.map_owner(|next| Node { value, next }, Semantics::Relaxed);
209 /// }
210 ///
211 /// let Node { value, mut next } = cell
212 /// .take(Semantics::Relaxed)
213 /// .expect("Some values should've been inserted into the cell");
214 ///
215 /// let mut decoded = value.to_string();
216 /// while let Some(node) = next.take(Semantics::Relaxed) {
217 /// decoded.extend([" ", node.value]);
218 /// next = node.next
219 /// }
220 ///
221 /// assert_eq!(decoded, "Hachó en México")
222 /// ```
223 ///
224 /// [1]: https://doc.rust-lang.org/std/string/struct.String.html
225 pub fn map_owner<F>(&self, new: F, order: Semantics)
226 where
227 F: FnOnce(Self) -> T,
228 T: AsMut<Self>,
229 {
230 let value_ptr = self.get_ptr(order);
231 let value = unsafe { Self::from_ptr(value_ptr) };
232
233 let owner_slot = Some(new(value));
234 let owner_ptr = Self::heap_leak(owner_slot);
235
236 let owner = unsafe { &mut *owner_ptr };
237 let value_ptr = owner.as_mut().value.get_mut();
238
239 loop {
240 let value_ptr_result = self.value.compare_exchange_weak(
241 *value_ptr,
242 owner_ptr,
243 order.read_write(),
244 order.read(),
245 );
246
247 let Err(modified) = value_ptr_result else {
248 break;
249 };
250
251 *value_ptr = modified;
252 core::hint::spin_loop();
253 }
254 }
255
256 /// Swaps the values of two cells
257 ///
258 /// # Usage
259 ///
260 /// ```rust
261 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
262 ///
263 /// let one: PtrCell<u8> = 1.into();
264 /// let mut two: PtrCell<u8> = 2.into();
265 ///
266 /// one.swap(&mut two, Relaxed);
267 ///
268 /// assert_eq!(two.take(Relaxed), Some(1));
269 /// assert_eq!(one.take(Relaxed), Some(2))
270 /// ```
271 #[inline]
272 pub fn swap(&self, other: &mut Self, order: Semantics) {
273 let other_ptr = other.get_ptr(Semantics::Relaxed);
274
275 unsafe {
276 let ptr = self.replace_ptr(other_ptr, order);
277 other.set_ptr(ptr, Semantics::Relaxed);
278 }
279 }
280
281 /// Takes out the cell's value
282 ///
283 /// # Usage
284 ///
285 /// ```rust
286 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
287 ///
288 /// let cell: PtrCell<u8> = 45.into();
289 ///
290 /// assert_eq!(cell.take(Relaxed), Some(45));
291 /// assert_eq!(cell.take(Relaxed), None)
292 /// ```
293 #[inline]
294 pub fn take(&self, order: Semantics) -> Option<T> {
295 self.replace(None, order)
296 }
297
298 /// Takes out the cell's pointer
299 ///
300 /// # Safety
301 ///
302 /// Not inherently unsafe. See [Pointer Safety][1]
303 ///
304 /// # Usage
305 ///
306 /// ```rust
307 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
308 ///
309 /// let cell: PtrCell<u8> = 45.into();
310 /// let ptr = cell.take_ptr(Relaxed);
311 ///
312 /// assert_eq!(unsafe { ptr_cell::PtrCell::heap_reclaim(ptr) }, Some(45));
313 /// assert_eq!(cell.take_ptr(Relaxed), std::ptr::null_mut())
314 /// ```
315 ///
316 /// [1]: https://docs.rs/ptr_cell/latest/ptr_cell/struct.PtrCell.html#pointer-safety
317 #[inline]
318 pub fn take_ptr(&self, order: Semantics) -> *mut T {
319 self.replace_ptr(core::ptr::null_mut(), order)
320 }
321
322 /// Inserts a value into the cell
323 ///
324 /// # Usage
325 ///
326 /// ```rust
327 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
328 ///
329 /// let cell = PtrCell::default();
330 /// cell.set(Some(1776), Relaxed);
331 ///
332 /// assert_eq!(cell.take(Relaxed), Some(1776))
333 /// ```
334 #[inline]
335 pub fn set(&self, slot: Option<T>, order: Semantics) {
336 let _ = self.replace(slot, order);
337 }
338
339 /// Inserts a pointer into the cell
340 ///
341 /// # Safety
342 ///
343 /// The pointed-to memory must conform to the [memory layout][1] used by [`Box`]
344 ///
345 /// # Usage
346 ///
347 /// ```rust
348 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
349 ///
350 /// let cell = PtrCell::default();
351 ///
352 /// let ptr = PtrCell::heap_leak(Some(1776));
353 /// unsafe { cell.set_ptr(ptr, Relaxed) };
354 ///
355 /// assert_eq!(cell.take(Relaxed), Some(1776))
356 /// ```
357 ///
358 /// [1]: https://doc.rust-lang.org/std/boxed/index.html#memory-layout
359 #[inline]
360 pub unsafe fn set_ptr(&self, ptr: *mut T, order: Semantics) {
361 self.value.store(ptr, order.write());
362 }
363
364 /// Replaces the cell's value
365 ///
366 /// # Usage
367 ///
368 /// ```rust
369 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
370 ///
371 /// let cell = PtrCell::from('a');
372 ///
373 /// assert_eq!(cell.replace(Some('b'), Relaxed), Some('a'));
374 /// assert_eq!(cell.take(Relaxed), Some('b'))
375 /// ```
376 #[inline]
377 #[must_use = "use `.set()` if you don't need the old value"]
378 pub fn replace(&self, slot: Option<T>, order: Semantics) -> Option<T> {
379 let new_leak = Self::heap_leak(slot);
380
381 unsafe {
382 let old_leak = self.replace_ptr(new_leak, order);
383 Self::heap_reclaim(old_leak)
384 }
385 }
386
387 /// Replaces the cell's pointer
388 ///
389 /// **WARNING: THIS FUNCTION WAS ERRONEOUSLY LEFT SAFE. IT'S UNSAFE AND WILL BE MARKED AS SUCH
390 /// IN THE NEXT MAJOR RELEASE**
391 ///
392 /// # Safety
393 ///
394 /// The pointed-to memory must conform to the [memory layout][1] used by [`Box`]
395 ///
396 /// See also: [Pointer Safety][2]
397 ///
398 /// # Usage
399 ///
400 /// ```rust
401 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
402 ///
403 /// unsafe {
404 /// let a = PtrCell::heap_leak(Some('a'));
405 /// let b = PtrCell::heap_leak(Some('b'));
406 ///
407 /// let cell = PtrCell::from_ptr(a);
408 ///
409 /// assert_eq!(cell.replace_ptr(b, Relaxed), a);
410 /// assert_eq!(cell.take_ptr(Relaxed), b);
411 ///
412 /// PtrCell::heap_reclaim(a);
413 /// PtrCell::heap_reclaim(b);
414 /// }
415 /// ```
416 ///
417 /// [1]: https://doc.rust-lang.org/std/boxed/index.html#memory-layout
418 /// [2]: https://docs.rs/ptr_cell/latest/ptr_cell/struct.PtrCell.html#pointer-safety
419 #[inline]
420 #[must_use = "use `.set_ptr()` if you don't need the old pointer"]
421 pub fn replace_ptr(&self, ptr: *mut T, order: Semantics) -> *mut T {
422 self.value.swap(ptr, order.read_write())
423 }
424
425 /// Mutably borrows the cell's value
426 ///
427 /// # Usage
428 ///
429 /// ```rust
430 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
431 ///
432 /// let mut text = PtrCell::from("Point".to_string());
433 /// text.get_mut()
434 /// .expect("The cell should contain a value")
435 /// .push_str("er");
436 ///
437 /// assert_eq!(text.take(Relaxed), Some("Pointer".to_string()))
438 /// ```
439 #[inline]
440 pub fn get_mut(&mut self) -> Option<&mut T> {
441 let leak = *self.value.get_mut();
442
443 non_null(leak).map(|ptr| unsafe { &mut *ptr })
444 }
445
446 /// Returns a pointer to the cell's value
447 ///
448 /// # Safety
449 ///
450 /// Not inherently unsafe. See [Pointer Safety][1]
451 ///
452 /// # Usage
453 ///
454 /// ```rust
455 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
456 ///
457 /// let cell = PtrCell::<[u8; 3]>::default();
458 ///
459 /// assert_eq!(cell.get_ptr(Relaxed), std::ptr::null_mut())
460 /// ```
461 ///
462 /// [1]: https://docs.rs/ptr_cell/latest/ptr_cell/struct.PtrCell.html#pointer-safety
463 #[inline]
464 pub fn get_ptr(&self, order: Semantics) -> *mut T {
465 self.value.load(order.read())
466 }
467
468 /// Determines whether this cell is empty
469 ///
470 /// # Usage
471 ///
472 /// ```rust
473 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
474 ///
475 /// let cell = PtrCell::<[u8; 3]>::default();
476 ///
477 /// assert!(cell.is_empty(Relaxed))
478 /// ```
479 #[inline]
480 pub fn is_empty(&self, order: Semantics) -> bool {
481 self.get_ptr(order).is_null()
482 }
483
484 /// Constructs a cell
485 ///
486 /// # Usage
487 ///
488 /// ```rust
489 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
490 ///
491 /// let cell = PtrCell::new(Some(0xFAA));
492 ///
493 /// assert_eq!(cell.take(Relaxed), Some(0xFAA));
494 /// assert!(cell.is_empty(Relaxed))
495 /// ```
496 #[inline]
497 #[must_use]
498 pub fn new(slot: Option<T>) -> Self {
499 let ptr = Self::heap_leak(slot);
500
501 unsafe { Self::from_ptr(ptr) }
502 }
503
504 /// Constructs a cell that owns [leaked](Self::heap_leak) memory
505 ///
506 /// A null pointer represents [`None`]
507 ///
508 /// # Safety
509 ///
510 /// The memory must conform to the [memory layout][1] used by [`Box`]
511 ///
512 /// # Usage
513 ///
514 /// ```rust
515 /// use ptr_cell::{PtrCell, Semantics::Relaxed};
516 ///
517 /// let ptr = PtrCell::heap_leak(Some(0xFAA));
518 /// let cell = unsafe { PtrCell::from_ptr(ptr) };
519 ///
520 /// assert_eq!(cell.take(Relaxed), Some(0xFAA));
521 /// assert!(cell.is_empty(Relaxed))
522 /// ```
523 ///
524 /// [1]: https://doc.rust-lang.org/std/boxed/index.html#memory-layout
525 #[inline]
526 pub const unsafe fn from_ptr(ptr: *mut T) -> Self {
527 let value = core::sync::atomic::AtomicPtr::new(ptr);
528
529 Self { value }
530 }
531
532 /// Reclaims ownership of [leaked](Self::heap_leak) memory
533 ///
534 /// A null pointer represents [`None`]
535 ///
536 /// # Safety
537 ///
538 /// The memory must conform to the [memory layout][1] used by [`Box`]
539 ///
540 /// Dereferencing `ptr` after this function has been called may cause undefined behavior
541 ///
542 /// # Usage
543 ///
544 /// ```rust
545 /// use ptr_cell::PtrCell;
546 ///
547 /// let ptr = PtrCell::heap_leak(Some(1155));
548 ///
549 /// assert_eq!(unsafe { PtrCell::heap_reclaim(ptr) }, Some(1155))
550 /// ```
551 ///
552 /// [1]: https://doc.rust-lang.org/std/boxed/index.html#memory-layout
553 #[inline]
554 pub unsafe fn heap_reclaim(ptr: *mut T) -> Option<T> {
555 non_null(ptr).map(|ptr| *unsafe { Box::from_raw(ptr) })
556 }
557
558 /// Leaks a value to the heap
559 ///
560 /// [`None`] is represented by a null pointer
561 ///
562 /// The memory will conform to the [memory layout][1] used by [`Box`]
563 ///
564 /// # Usage
565 ///
566 /// ```rust
567 /// use ptr_cell::PtrCell;
568 ///
569 /// let ptr = PtrCell::heap_leak(Some(1155));
570 ///
571 /// assert_eq!(unsafe { PtrCell::heap_reclaim(ptr) }, Some(1155))
572 /// ```
573 ///
574 /// [1]: https://doc.rust-lang.org/std/boxed/index.html#memory-layout
575 #[inline]
576 #[must_use]
577 pub fn heap_leak(slot: Option<T>) -> *mut T {
578 match slot {
579 Some(value) => Box::into_raw(Box::new(value)),
580 None => core::ptr::null_mut(),
581 }
582 }
583}
584
585impl<T> core::fmt::Debug for PtrCell<T> {
586 fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
587 formatter
588 .debug_struct("PtrCell")
589 .field("value", &self.value)
590 .finish()
591 }
592}
593
594impl<T> Default for PtrCell<T> {
595 /// Constructs an empty cell
596 #[inline]
597 fn default() -> Self {
598 Self::new(None)
599 }
600}
601
602impl<T> Drop for PtrCell<T> {
603 #[inline]
604 fn drop(&mut self) {
605 let ptr = *self.value.get_mut();
606
607 unsafe { Self::heap_reclaim(ptr) };
608 }
609}
610
611impl<T> From<T> for PtrCell<T> {
612 #[inline]
613 fn from(value: T) -> Self {
614 Self::new(Some(value))
615 }
616}
617
618/// Returns `ptr` if it's non-null
619#[inline]
620fn non_null<T>(ptr: *mut T) -> Option<*mut T> {
621 if ptr.is_null() {
622 None
623 } else {
624 Some(ptr)
625 }
626}
627
628/// Memory ordering semantics for atomic operations
629///
630/// Each variant represents a group of compatible [orderings](Ordering). They determine how value
631/// updates are synchronized between threads
632#[non_exhaustive]
633#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
634pub enum Semantics {
635 /// [`Relaxed`](Ordering::Relaxed) semantics
636 ///
637 /// No synchronization constraints and the best performance
638 ///
639 /// Set this when using a value in only one thread
640 Relaxed,
641
642 /// [`Release`](Ordering::Release) - [`Acquire`](Ordering::Acquire) coupling semantics
643 ///
644 /// Mild synchronization constraints and fair performance
645 ///
646 /// A read will always see the preceding write (if one exists). Any operations that take place
647 /// before the write will also be seen, regardless of their semantics
648 ///
649 /// Set this when using a value in multiple threads, unless certain that you need different
650 /// semantics
651 #[default]
652 Coupled,
653
654 /// [`SeqCst`](Ordering::SeqCst) semantics
655 ///
656 /// Maximum synchronization constraints and the worst performance
657 ///
658 /// All memory operations will appear to be executed in a single, total order
659 Ordered,
660}
661
662/// Implements a method on [`Semantics`] that returns the appropriate [`Ordering`] for a type of
663/// operations
664macro_rules! operation {
665 ($name:ident with $coupled:path:
666 { $($overview:tt)* }, { $($returns:tt)* }, { $($assert:tt)* }
667 $(,)? ) => {
668 impl Semantics {
669 $($overview)*
670 ///
671 /// # Returns
672 /// - [`Relaxed`](Ordering::Relaxed) for [`Relaxed`](Semantics::Relaxed) semantics
673 $($returns)*
674 /// - [`SeqCst`](Ordering::SeqCst) for [`Ordered`](Semantics::Ordered) semantics
675 ///
676 /// # Usage
677 ///
678 /// ```rust
679 /// use ptr_cell::Semantics::Coupled;
680 /// use std::sync::atomic::Ordering;
681 ///
682 $($assert)*
683 /// ```
684 #[inline]
685 pub const fn $name(&self) -> Ordering {
686 match self {
687 Self::Relaxed => Ordering::Relaxed,
688 Self::Coupled => $coupled,
689 Self::Ordered => Ordering::SeqCst,
690 }
691 }
692 }
693 };
694}
695
696// Asserts are missing a space on purpose. All whitespace after `///` seems to be carried over to
697// the example
698
699operation!(read_write with Ordering::AcqRel: {
700 /// Returns the memory ordering for read-write operations with these semantics
701}, {
702 /// - [`AcqRel`](Ordering::AcqRel) for [`Coupled`](Semantics::Coupled) semantics
703}, {
704 ///assert_eq!(Coupled.read_write(), Ordering::AcqRel)
705});
706
707operation!(write with Ordering::Release: {
708 /// Returns the memory ordering for write operations with these semantics
709}, {
710 /// - [`Release`](Ordering::Release) for [`Coupled`](Semantics::Coupled) semantics
711}, {
712 ///assert_eq!(Coupled.write(), Ordering::Release)
713});
714
715operation!(read with Ordering::Acquire: {
716 /// Returns the memory ordering for read operations with these semantics
717}, {
718 /// - [`Acquire`](Ordering::Acquire) for [`Coupled`](Semantics::Coupled) semantics
719}, {
720 ///assert_eq!(Coupled.read(), Ordering::Acquire)
721});