Rc<T>
heap-allocates an RcBox<T>
, that is, a struct with three elements in it: the strong_count
and weak_count
counters, and an inlined T
.
When strong_count
reaches 0, ptr::drop_in_place::<T>(&mut inlined_T)
, thus recursively dropping the memory owned by T
, but not freeing T itself (it can't, since the remaining Weak<T>
need to read the weak_count
value.
Example
use ::std::rc::{Rc, Weak};
let x1: Rc<String> = Rc::new(String::from("Foo"));
let x2: Rc<String> = x1.clone();
let w: Weak<String> = Rc::downgrade(&x1);
Here is a diagram representing the memory layout:
// 1.
mem::drop(x1);
// 2.
mem::drop(x2);
// 3.
mem::drop(w);
-
x1
is dropped
strong_count > 1
, so it is just decremented and nothing else happens. -
x2
is dropped
strong_count == 1
soptr::drop_in_place(&mut inner)
is called, and the heap-allocatedstr
bytes are freed. Thenstrong_count
is decremented, and sinceweak_count > 0
, theRcBox<String>
is still around (althoughinner: String
is now invalid data). -
w
is dropped
weak_count
is decremented and goes down to0
, so it checks ifstrong_count == 0
. It is the case, so theRcBox<String>
is freed.
Conclusion
The memory (RcBox<T>
) is only freed when all the counters drop (pun intended ) down to 0, but the data "stops being owned" (T
is dropped) as soon as strong_count
reaches 0
, making a Rc <-> Weak
cycle not leak memory.
Note: this behavior is exactly the same for Arc
, the only thing that changes is the way the counters are incremented / decremented (using atomic operations to avoid breaking coherence).