&* on a Weak<T>, Weak, RC, Drop, Free

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:

rc_string

// 1.
mem::drop(x1);
// 2.
mem::drop(x2);
// 3.
mem::drop(w);
  1. x1 is dropped
    strong_count > 1, so it is just decremented and nothing else happens.

  2. x2 is dropped
    strong_count == 1 so ptr::drop_in_place(&mut inner) is called, and the heap-allocated str bytes are freed. Then strong_count is decremented, and since weak_count > 0, the RcBox<String> is still around (although inner: String is now invalid data).

  3. w is dropped
    weak_count is decremented and goes down to 0, so it checks if strong_count == 0. It is the case, so the RcBox<String> is freed.


Conclusion

The memory (RcBox<T>) is only freed when all the counters drop (pun intended :stuck_out_tongue:) 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).

8 Likes