[Day39] Read Rust Atomics and Locks - Upgrade Arc with Weak ability
by Mara Bos
At Topic: Chapter 6. Weak Pointers
A Weak<T> aka. weak pointer provides a solution for dropping "cyclic structure"
Recall
Arc
- Goal: To share ownership
- Thread safe (While
Rcis not) - Immutable (So is
Rc)
let a = Arc::new([1, 2, 3]); let b = a.clone(); assert_eq!(a.as_ptr(), b.as_ptr()); // Same allocation!
UnsafeCell
- An
UnsafeCellis the primitive building unit for interior mutability - No restrictions to avoid undefined behavior
- Can only be used in
unsafeblock - Commonly, an
UnsafeCellis wrapped in another type that provides safety through a limited interface, such asCellorMutex. - In other words, all types with interior mutability are built on top of
UnsafeCell, Including:Cell,RefCell,RwLock,Mutex... - Gets a mutable raw pointer (
*mut T) to the wrapped value bygetmethod.
pub const fn get(&self) -> *mut T
Compare-and-Exchange Operations
- Compare-and-Exchange checks if the atomic value is equal to a given value, and only if that is the case does it replace it with a new value (as a single operation)
compare_exchange_weak: Similar tocompare_exchange, but the difference is that the weak version may still sometimes leave the value untouched and return an Err, even though the atomic value matched the expected value.- We could use Compare-and-Exchange to implement all the other atomic operations by putting it in a loop to retry if it did change
// source: https://doc.rust-lang.org/std/sync/atomic/struct.AtomicUsize.html#examples-11 use std::sync::atomic::{AtomicUsize, Ordering}; let some_var = AtomicUsize::new(5); // some_var compares to the first argument (5) and if it is equal, it will be replaced by the second argument (10) assert_eq!(some_var.compare_exchange(5, 10, Ordering::Acquire, Ordering::Relaxed), Ok(5)); assert_eq!(some_var.load(Ordering::Relaxed), 10);
Notes
To see full implementation, please go here. I just write down things that I feel important.
Weak<T>behaves similar toArc<T>, but does not prevent an object from getting dropped =>Weak<T>can exist without aTWeak<T>can be upgraded toArc<T>through itsupgrademethod, but only if theTstill exists.- To provide
Arcas well asWeak, it is needed to modifyArcDatastruct:
// Before struct ArcData<T> { ref_count: AtomicUsize, data: T, } // After struct ArcData<T> { /// Number of `Arc`s. data_ref_count: AtomicUsize, /// Number of `Arc`s and `Weak`s combined. alloc_ref_count: AtomicUsize, /// The data. `None` if there's only weak pointers left. data: UnsafeCell<Option<T>>, }
- When implementing
DerefforArc, note that the type ofArcData.datais nowUnsafeCell<Option<T>>rather thanT. Therefore, it takes more steps to get&T:
// Instead of fn deref(&self) -> &T { &self.data().data } // It should be fn deref(&self) -> &T { // Using `UnsafeCell::get()` let ptr = self.weak.data().data.get(); // Safty: Since there's an Arc to the data, // the data exists and may be shared. // (*ptr): *mut Option<T> => Option<T> // (*ptr).as_ref(): &Option<T> to Option<&T> unsafe { (*ptr).as_ref().unwrap() } }
- There are 2 focal points about
Arc::get_mut:
struct ArcData<T> { data_ref_count: AtomicUsize, alloc_ref_count: AtomicUsize, // key for checking if it's the only Arc data: UnsafeCell<Option<T>>, } impl<T> Arc<T> { pub fn get_mut(arc: &mut Self) -> Option<&mut T> { if arc.weak.data().alloc_ref_count.load(Relaxed) == 1 { fence(Acquire); // Safety: Nothing else can access the data, since // there's only one Arc, to which we have exclusive access, // and no Weak pointers. let arcdata: &mut ArcData<T> = unsafe { arc.weak.ptr.as_mut() }; let option: &mut Option<T> = arcdata.data.get_mut(); // We know the data is still available since we // have an Arc to it, so this won't panic. let data: &mut T = option.as_mut().unwrap(); Some(data) } else { None } } }
- By
alloc_ref_count, we can check bothArcandWeakcount at the same time. Ifalloc_ref_countis 1, it means there's only oneArcand noWeakpointers. So, it's safe to get mutable access to the data. - (My guess)
arc.weak.ptr.as_mut()is used rather thanarc.weak.data()because we need&mut ArcData<T>rather than&ArcData<T>.
- (Quote from the book) Dropping an object in Rust will first run its Drop::drop function (if it implements Drop), and then drop all of its fields, one by one, recursively.
Questions
Why does not Weak implemented Deref?
According to Claude
- Safety considerations: Weak pointers don't guarantee that the data they point to still exists. If
WeakimplementedDeref, users might incorrectly assume that the data is always available, which could lead to unsafe behavior. - Semantic mismatch:
Derefis typically used to represent some kind of "ownership" or "guaranteed existence" relationship. Weak pointers fundamentally don't provide this guarantee. - Different usage patterns:
Arcneeds frequent access to its internal data, soDerefis useful.Weakis primarily used to check if data exists and potentially upgrade to anArc, not for direct access.
My Difficulty
- I know
UnsafeCellsupplies more control and lower overhead and is great for synchronization. But the timing to use it rather thanCellorRefCellis quite difficult! - When to know my customized struct needs to implement
Drop?