Is the dereference to a pointer that points to a struct having a dropped field considered UB?

Consider the example in the implementation of std::sync::Weak

#[repr(C)]
struct ArcInner<T: ?Sized> {
    strong: atomic::AtomicUsize,
    weak: atomic::AtomicUsize,
    data: T,
}
#[stable(feature = "arc_weak", since = "1.4.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "ArcWeak")]
pub struct Weak<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
> {
    ptr: NonNull<ArcInner<T>>,
    alloc: A,
}

impl<T: ?Sized, A: Allocator> Weak<T, A> {
    #[inline]
    fn inner(&self) -> Option<WeakInner<'_>> {
        let ptr = self.ptr.as_ptr();
        if is_dangling(ptr) {
            None
        } else {
            // We are careful to *not* create a reference covering the "data" field, as
            // the field may be mutated concurrently (for example, if the last `Arc`
            // is dropped, the data field will be dropped in-place).
            Some(unsafe { WeakInner { strong: &(*ptr).strong, weak: &(*ptr).weak } })
        }
    }
}

The comment says that the intent is to avoid creating a reference covering the "data" field. However, ptr has type NonNull<ArcInner<T>>, doesn't the dereference *ptr produce a reference to ArcInner<T> which covers the "data" field? If *ptr does not produce that reference, why is it necessary to create WeakInner<'_>? (*(self.ptr)).strong or (*(self.ptr)).weak is sufficient to manipulate the strong and weak fields, which does not touch the potentially corrupt data?

A dereference doesn't immediately do anything, it's what's called a "place expression". When you immediately reference again like that, you're not actually creating a reference to the inner type, because the reference operation is acting on the whole right side. The right side is a place expression that resolves to ptr.strong.

My guess on WeakInner is that it's more of an ergonomics and robustness thing (making sure you get it right) rather than an essential feature. However, I haven't read the full source code for Arc, so I'm not sure.

1 Like

Ok, that means we theoretically don't need to create WeakInner, instead, we can directly use (*(self.ptr)).weak and (*(self.ptr)).strong to access the weak and strong, as long as we promise not to access data field, right?

I believe so, yes. That's equivalent to (*self.ptr).strong, which is definitely valid according to this implementation.

1 Like