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);
-
x1is dropped
strong_count > 1, so it is just decremented and nothing else happens. -
x2is dropped
strong_count == 1soptr::drop_in_place(&mut inner)is called, and the heap-allocatedstrbytes are freed. Thenstrong_countis decremented, and sinceweak_count > 0, theRcBox<String>is still around (althoughinner: Stringis now invalid data). -
wis dropped
weak_countis 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).
