Search code examples
rustmemory-managementsmart-pointersweak-referencesreference-counting

In Rust, how does Weak<T> know when the inner value has been dropped?


std::rc::Weak<T> has the following definition:

pub struct Weak<T: ?Sized> {
    ptr: NonNull<RcBox<T>>,
}

In my understanding, when there's no more Rc<T> left, RcBox<T> will be freed and Weak<T>.ptr now points to a place that could potentially contain anything. So when upgrade() is called on a Weak<T>, how does it know the pointer is now invalid?

Weak<T>::upgrade has the following implementation:

    pub fn upgrade(&self) -> Option<Rc<T>> {
        let inner = self.inner()?;

        if inner.strong() == 0 {
            None
        } else {
            unsafe {
                inner.inc_strong();
                Some(Rc::from_inner(self.ptr))
            }
        }
    }

Does this imply RcBox<T> is not really freed when there's no Rc<T> left in the wild? If so, wouldn't that leak memory?


Solution

  • The RcBox is not destroyed until there are no strong or weak pointers remaining.

    However, the contained T is dropped in place when the strong count reaches zero. This effectively destroys the contained T but does not release the memory of the T itself. This is how Weak knows if the inner value was dropped -- if you try to upgrade a Weak to an Rc when the strong count is zero, the operation fails and returns None.

    Once the weak count also reaches zero, the RcBox itself is freed. So no, there is no memory leak.

    Note that many types (such as String and Vec) manage a separate heap allocation. If you have an Rc<Vec<_>>, for example, when the strong count reaches zero but the weak count does not, the drop code for the inner Vec runs and drops all of its owned elements as well as frees the heap allocation used to store them. The memory used by the Vec itself to hold the heap pointer, length, and capacity is not freed as it is owned by the RcBox. That allocation is only freed when the strong and weak counts both reach zero.