Why do you need a weak_count counter for Rc.? strong_count is needed to know when to free up memory. And what is the weak_count counter for?
I assume that during drop Rc changes all data inside Weak to None if weak_count > 0
Why do you need a weak_count counter for Rc.? strong_count is needed to know when to free up memory. And what is the weak_count counter for?
I assume that during drop Rc changes all data inside Weak to None if weak_count > 0
When using an Rc, there are two different things that need to be cleaned up:
This is because a Weak<T>
still has a pointer to the allocation, and it needs to continue to be able to access it so that it can read the strong and weak counts. If you deallocated it when there are still weak references, then trying to upgrade them would be a use-after-free.
Without Weak
, self-referential data types would never be dropped (one Rc
holding the other Rc
and vice versa, the strong_count
of either one would never reach 0). From the docs:
A
Weak
pointer is useful for keeping a temporary reference to the allocation managed byRc
without preventing its inner value from being dropped. It is also used to prevent circular references betweenRc
pointers, since mutual owning references would never allow eitherRc
to be dropped. For example, a tree could have strongRc
pointers from parent nodes to children, andWeak
pointers from children back to their parents.
The thing that Weak
needs access to is some shared data that tracks whether or not the Rc
’s value still exists. After all, there needs to be some way to implement Weak::upgrade
. The weak count is then necessary to determine when this shared data can be dropped. What kind of data exactly is shared for this purpose is of course another implementation detail, in practice Rust (currently) uses the strong count, not only as a way to know when the Rc
’s value can be dropped, but also as an indicator to Weak
pointers whether or not the value still exists and thus whether .upgrade()
can succeed.
Furthermore, the strong counter and the weak counter are bundled together with the Rc
’s value into a single allocation, because this allows Rc
and Weak
to remain small (single pointer), whilst also avoiding multiple-indirections, and it also allows keep the number of allocation and deallocation operations lower. Of course, for Rc<T>
where mem::size_of::<T>()
is relatively large, like e.g. Rc<[u8; 1000000]>
, this trade-off has the disadvantage that the memory needed to hold the value, is not immediately freed when the value is dropped (by the last Rc
being dropped), but only at a later point (when the last Weak
is dropped). But for the vast majority of types, their (shallow) size is small enough that it’s a no-brainer to accept this small disadvantage, and in the worst case, you could always fall back to something like Rc<Box<[u8; 1000000]>>
to avoid the problem (with the disadvantage of introducing additional indirection).
thank you for such a detailed explanation
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.