Smart Pointers

Weak<T>

Weak<T> (std::rc::Weak<T> for single-threaded code and std::sync::Weak<T> for multi-threaded code) is a non-owning smart pointer that references an Rc<T> or Arc<T> allocation without preventing the inner value T from being dropped.

Strong vs. Weak Ownership Semantics

An Rc<T> or Arc<T> represents strong ownership: as long as strong_count >= 1, the inner value T stays alive. A Weak<T> represents contingent access (observation or back-reference):

  • Creating a Weak<T> via Rc::downgrade(&rc) or Arc::downgrade(&arc) increments weak_count, leaving strong_count unchanged.
  • Weak<T> does not implement Deref because the target T may have already been dropped.
  • To read the value, callers must call .upgrade(), which returns Option<Rc<T>> (or Option<Arc<T>>):
    • If strong_count >= 1, .upgrade() increments strong_count and returns Some(rc).
    • If strong_count == 0, .upgrade() returns None.
rc1: Rc<T> (strong_count = 1)
   |
   | Rc::downgrade(&rc1)
   v
weak: Weak<T> (weak_count = 1)
   |
   +---> While rc1 is alive:  weak.upgrade() -> Some(Rc<T>) (strong_count becomes 2)
   +---> After rc1 is dropped: weak.upgrade() -> None
use std::rc::{Rc, Weak};

fn main() {
    let strong: Rc<String> = Rc::new(String::from("ephemeral cache entry"));
    let weak: Weak<String> = Rc::downgrade(&strong);

    if let Some(upgraded) = weak.upgrade() {
        println!(
            "while strong exists -> value: {:?}, strong_count: {}",
            upgraded,
            Rc::strong_count(&upgraded)
        );
    }

    // Drop the sole strong owner:
    drop(strong);

    println!("after drop -> upgrade(): {:?}", weak.upgrade());
}
while strong exists -> value: "ephemeral cache entry", strong_count: 2
after drop -> upgrade(): None

Core Weak<T> Methods

Method Signature (rc::Weak / sync::Weak) Behavior
Weak::new() const fn new() -> Weak<T> Creates a dangling Weak<T> without allocating on the heap (upgrade() always returns None)
Rc::downgrade(&rc) fn downgrade(this: &Rc<T>) -> Weak<T> Creates a Weak<T> pointing to this's allocation
.upgrade() fn upgrade(&self) -> Option<Rc<T>> Attempts to promote Weak<T> into a strong Rc<T> / Arc<T>
.strong_count() fn strong_count(&self) -> usize Returns the number of live strong pointers (0 if value was dropped)
.weak_count() fn weak_count(&self) -> usize Returns the number of live weak pointers (0 if no strong pointers remain or dangling)
.ptr_eq(&other) fn ptr_eq(&self, other: &Weak<T>) -> bool Returns true if both Weak<T> pointers refer to the same allocation

Self-Referential Initialization with Rc::new_cyclic and Arc::new_cyclic

Rc::new_cyclic and Arc::new_cyclic pass a &Weak<T> to a constructor closure before the T value finishes initializing. Once the closure returns T, the Weak<T> becomes upgradable, allowing a struct or graph node to store a non-owning weak reference to itself or to child nodes created during construction without needing an Option wrapper:

use std::rc::{Rc, Weak};

struct Gadget {
    id: u32,
    me: Weak<Gadget>,
}

impl Gadget {
    fn new(id: u32) -> Rc<Self> {
        Rc::new_cyclic(|weak_self| Gadget {
            id,
            me: Weak::clone(weak_self),
        })
    }

    fn identify(&self) -> u32 {
        self.me.upgrade().unwrap().id
    }
}

fn main() {
    let gadget = Gadget::new(42);
    println!("gadget id via self-weak: {}", gadget.identify());
    println!(
        "strong_count: {}, weak_count: {}",
        Rc::strong_count(&gadget),
        Rc::weak_count(&gadget)
    );
}
gadget id via self-weak: 42
strong_count: 1, weak_count: 1