Smart Pointers

Rc<T>

std::rc::Rc<T> (Reference Counted) is a single-threaded smart pointer that enables multiple owners of the same heap-allocated value T. It tracks the number of active owners at runtime and drops the value and its heap allocation only when the last Rc<T> pointing to it is dropped.

Memory Layout of Rc<T>

Calling Rc::new(value) performs a single heap allocation containing an internal RcBox<T> structure with two usize reference counts (strong and weak) followed immediately by the payload T. Each Rc<T> handle on the stack is a single thin pointer (8 bytes on 64-bit targets) pointing to the heap RcBox<T>.

Stack Handles                     Single Heap Allocation (RcBox<T>)
+---------------+                 +-------------------------+
| rc1: Rc<T> ---+---------------> | strong: Cell<usize> (2) |
+---------------+       +-------> | weak:   Cell<usize> (1) |
                        |         | value:  T               |
+---------------+       |         +-------------------------+
| rc2: Rc<T> ---+-------+
+---------------+

Cloning an Rc<T> vs. Cloning T

Cloning an Rc<T> increments the strong reference count by 1 and returns a new 8-byte pointer to the same heap allocation; it never copies or clones the underlying T. By convention, Rc::clone(&rc) is preferred over rc.clone() so readers can immediately see that only a cheap pointer-and-counter increment is taking place.

use std::rc::Rc;

fn main() {
    let a = Rc::new(String::from("shared config"));
    println!("initial strong count: {}", Rc::strong_count(&a));

    let b = Rc::clone(&a);
    println!("after cloning b:      {}", Rc::strong_count(&a));

    {
        let c = Rc::clone(&a);
        println!("inside inner scope:   {}", Rc::strong_count(&c));
        println!("value via c:          {}", c);
    }

    println!("after c is dropped:   {}", Rc::strong_count(&a));
    println!("b and a same addr:    {}", Rc::ptr_eq(&a, &b));
}
initial strong count: 1
after cloning b:      2
inside inner scope:   3
value via c:          shared config
after c is dropped:   2
b and a same addr:    true

Essential Rc<T> Associated Functions

Function Signature Behavior
Rc::new(value) fn new(value: T) -> Rc<T> Allocates RcBox<T> on the heap with strong = 1
Rc::clone(&this) fn clone(this: &Rc<T>) -> Rc<T> Increments strong count and returns a new handle
Rc::strong_count(&this) fn strong_count(this: &Rc<T>) -> usize Returns current number of owning Rc<T> handles
Rc::weak_count(&this) fn weak_count(this: &Rc<T>) -> usize Returns current number of active Weak<T> handles
Rc::downgrade(&this) fn downgrade(this: &Rc<T>) -> Weak<T> Creates a non-owning Weak<T> pointer
Rc::get_mut(&mut this) fn get_mut(this: &mut Rc<T>) -> Option<&mut T> Returns Some(&mut T) only if strong == 1 and weak == 0
Rc::make_mut(&mut this) fn make_mut(this: &mut Rc<T>) -> &mut T Returns &mut T, cloning T into a fresh Rc if shared (copy-on-write)
Rc::try_unwrap(this) fn try_unwrap(this: Rc<T>) -> Result<T, Rc<T>> Moves T out of Rc<T> if strong == 1; otherwise returns Err(this)

Copy-on-Write Mutation with Rc::make_mut and Rc::get_mut

Because Rc<T> can have multiple readers at once, Deref only yields an immutable reference &T. However, when an Rc<T> is the sole owner (strong_count == 1 and weak_count == 0), Rc::get_mut grants direct &mut T access in place, and Rc::make_mut automatically clones the inner T on write when shared by multiple owners:

use std::rc::Rc;

fn main() {
    let mut primary = Rc::new(vec![1, 2, 3]);

    // Sole owner: `get_mut` mutates in place without cloning the Vec
    if let Some(vec_mut) = Rc::get_mut(&mut primary) {
        vec_mut.push(4);
    }
    println!("primary (unique): {:?}", primary);

    // Create a second owner:
    let snapshot = Rc::clone(&primary);

    // Shared owner: `make_mut` clones the Vec into a new allocation for `primary`
    Rc::make_mut(&mut primary).push(5);

    println!("snapshot (unchanged): {:?}", snapshot);
    println!("primary (branched):   {:?}", primary);
}
primary (unique): [1, 2, 3, 4]
snapshot (unchanged): [1, 2, 3, 4]
primary (branched):   [1, 2, 3, 4, 5]

Why Rc<T> Cannot Cross Threads (!Send and !Sync)

Rc<T> stores its reference counts in non-atomic Cell<usize> fields so increments and decrements compile to ordinary CPU integer instructions without atomic synchronization overhead. Because concurrent non-atomic increments from two threads would cause a data race and corrupt the reference count, Rc<T> implements neither Send nor Sync.