| |

Rust 57 🦀 Reference Counting Smart Pointer (Rc)

⏱️ Estimated read: 20 min | |

Ownership in Rust is single. Every value has exactly one owner, and when the owner goes out of scope, the value is dropped. This rule is what makes the borrow checker predictable, but it breaks down when a value must be shared. A graph node referenced by several edges, a tree node with two parents, or a configuration object held by many components: these need multiple owners for the same value. The Rc<T> smart pointer solves this. It keeps a count of how many owners a value has, and it drops the value only when the count reaches zero. The count is not atomic, which makes Rc cheap but single-threaded, and the value behind it is immutable by default, which means mutation requires an interior-mutability type like RefCell.

Key point: Rc<T> provides shared ownership on a single thread. The reference count is a plain integer, incremented and decremented on clone and drop. It is not atomic, so it is not thread-safe. For multithreaded sharing, use Arc<T>.


Why Rc exists

The multiple-owner problem. The ownership rules allow exactly one owner for a value. Some data structures need more. A node in a doubly linked list has a next pointer and a prev pointer, and both point to nodes that are also pointed to by other nodes. The ownership graph has cycles, or at least multiple parents, and a strict tree of owners cannot represent it.

The shared-immutable problem. A configuration object is read by many components and mutated by none. The components need references to the same value, and the value must live as long as any of them holds a reference. Rc<T> provides exactly this: shared immutable access with automatic lifetime extension.

The clone problem. Cloning a large value copies it. Cloning an Rc<T> copies only the pointer and increments the count. The shared value is not duplicated, which is what makes Rc cheap even when the underlying value is large.

The drop problem. An Rc<T> value is dropped when the last Rc pointing to it is dropped. The reference count is what tracks this. If the count is wrong, the value is either leaked (dropped too late) or freed while still in use (dropped too early). The Rc implementation maintains the count correctly.

The cycle problem. An Rc<T> cycle leaks. If two Rc values point to each other, each holds the other’s count above zero, and neither is dropped. The Weak<T> type breaks the cycle by holding a reference that does not contribute to the count.

The thread-safety problem. The Rc count is a Cell<usize>, which is not atomic. Incrementing and decrementing from two threads is a data race. The Arc<T> type uses an AtomicUsize, which is safe but slower. Rc is the single-threaded choice.

The interior-mutability problem. Rc<T> allows shared access but not mutable access. Rc<RefCell<T>> combines the two: the Rc provides shared ownership, and the RefCell provides runtime borrow checking. The combination is the standard pattern for mutable shared data on a single thread.


a. The Rc API

Rc<T> is a smart pointer that wraps a value and a reference count. The pointer is created with Rc::new, cloned with Rc::clone, and inspected with Rc::strong_count and Rc::weak_count.

The creation

use std::rc::Rc;

fn main() {
    let value = Rc::new(5);
    println!("{}", value);  // 5
    println!("{}", Rc::strong_count(&value));  // 1
}

The Rc::new function allocates the value on the heap and initializes the strong count to 1. The value is accessed through the Rc, which derefs to the inner value.

The clone

fn main() {
    let a = Rc::new(5);
    let b = Rc::clone(&a);
    println!("{}", Rc::strong_count(&a));  // 2
}

The Rc::clone function is the idiomatic way to clone. It is a function call, not a method, and it is deliberately named to distinguish it from a.clone(), which would also clone the inner value if the inner value implements Clone.

The strong count

fn main() {
    let a = Rc::new(5);
    let b = Rc::clone(&a);
    let c = Rc::clone(&a);
    println!("{}", Rc::strong_count(&a));  // 3
}

The Rc::strong_count function returns the number of Rc values that own the same allocation.

The drop

fn main() {
    let a = Rc::new(5);
    let b = Rc::clone(&a);
    println!("{}", Rc::strong_count(&a));  // 2
    drop(b);
    println!("{}", Rc::strong_count(&a));  // 1
}

The drop function decrements the count. When the count reaches zero, the value is dropped.

The deref

fn main() {
    let a = Rc::new(vec![1, 2, 3]);
    println!("{}", a.len());  // 3, deref to Vec
}

The Rc<T> implements Deref<Target = T>, so method calls and field accesses reach the inner value.

The try_unwrap

fn main() {
    let a = Rc::new(5);
    let value = Rc::try_unwrap(a).ok();
    println!("{:?}", value);  // Some(5)
}

The Rc::try_unwrap function returns the inner value if the count is 1. If the count is greater, it returns the Rc in an Err.

The into_raw and from_raw

use std::rc::Rc;

fn main() {
    let a = Rc::new(5);
    let raw = Rc::into_raw(a);
    let b = unsafe { Rc::from_raw(raw) };
    println!("{}", b);  // 5
}

The into_raw function consumes the Rc and returns the raw pointer without decrementing the count. The from_raw function reconstructs the Rc from the pointer. The pair is used in unsafe code that needs to pass the pointer through a C API.

The ptr_eq

use std::rc::Rc;
use std::ptr;

fn main() {
    let a = Rc::new(5);
    let b = Rc::clone(&a);
    let c = Rc::new(5);
    println!("{}", Rc::ptr_eq(&a, &b));  // true
    println!("{}", Rc::ptr_eq(&a, &c));  // false
}

The Rc::ptr_eq function returns true when two Rc values point to the same allocation. The == operator compares the inner values, which is a different check.


b. The Rc patterns

Rc<T> is used in a small set of recurring patterns. The patterns are the reason the type exists.

The shared immutable data

use std::rc::Rc;

struct Config {
    name: String,
    version: u32,
}

fn main() {
    let config = Rc::new(Config {
        name: "app".to_string(),
        version: 1,
    });

    let a = Rc::clone(&config);
    let b = Rc::clone(&config);

    println!("{}", a.name);
    println!("{}", b.version);
}

The Config is created once and shared by reference. The value is not duplicated.

The Rc in a struct field

use std::rc::Rc;

struct Node {
    value: i32,
    next: Option<Rc<Node>>,
}

fn main() {
    let second = Rc::new(Node { value: 2, next: None });
    let first = Rc::new(Node { value: 1, next: Some(Rc::clone(&second)) });

    println!("{}", first.value);
    println!("{}", first.next.as_ref().unwrap().value);
}

The next field is an Option<Rc<Node>>, which allows a node to point to another node. The Rc provides the shared ownership.

The Rc with RefCell

use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let value = Rc::new(RefCell::new(5));
    let a = Rc::clone(&value);
    let b = Rc::clone(&value);

    *a.borrow_mut() += 1;
    *b.borrow_mut() += 1;

    println!("{}", value.borrow());  // 7
}

The Rc<RefCell<T>> combination provides shared mutable access. The RefCell enforces the borrow rules at runtime.

The Rc in a tree

use std::rc::Rc;

struct TreeNode {
    value: i32,
    children: Vec<Rc<TreeNode>>,
}

fn main() {
    let leaf = Rc::new(TreeNode { value: 1, children: vec![] });
    let root = Rc::new(TreeNode {
        value: 0,
        children: vec![Rc::clone(&leaf)],
    });

    println!("{}", root.children[0].value);
}

The tree’s children are Vec<Rc<TreeNode>>. A node can be shared by multiple parents, which the strict ownership model cannot represent.

The Rc cycle

use std::rc::Rc;
use std::cell::RefCell;

struct Node {
    value: i32,
    next: RefCell<Option<Rc<Node>>>,
}

fn main() {
    let a = Rc::new(Node { value: 1, next: RefCell::new(None) });
    let b = Rc::new(Node { value: 2, next: RefCell::new(Some(Rc::clone(&a))) });
    *a.next.borrow_mut() = Some(Rc::clone(&b));
    // The cycle leaks: neither a nor b is dropped.
}

The cycle is the classic Rc leak. The Weak<T> type is the fix.

The Rc with Weak

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

struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let parent = Rc::new(Node {
        value: 1,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    let child = Rc::new(Node {
        value: 2,
        parent: RefCell::new(Rc::downgrade(&parent)),
        children: RefCell::new(vec![]),
    });

    parent.children.borrow_mut().push(Rc::clone(&child));

    if let Some(p) = child.parent.borrow().upgrade() {
        println!("{}", p.value);  // 1
    }
}

The Weak reference does not contribute to the strong count, so the cycle does not leak. The upgrade method returns an Option<Rc<T>> that is Some if the value is still alive.

The Rc pattern table

PatternTypeUse Case
Shared immutableRc<T>Configuration, constants
Linked structureRc<Node>Trees, graphs without cycles
Shared mutableRc<RefCell<T>>Single-threaded mutable state
Cycle-safeRc<T> + Weak<T>Parent pointers, back references

c. Rc versus other pointers

Rc<T> is one of several smart pointers. The choice depends on the ownership and the threading requirements.

The Rc versus Box

Box<T> has a single owner. Rc<T> has multiple owners. A Box is the default when the value has one owner; an Rc is used when the value must be shared.

The Rc versus Arc

Arc<T> is the atomic version of Rc<T>. The count is an AtomicUsize, so it is safe to share across threads. The cost is a slower increment and decrement, which matters in tight loops. The rule is: use Rc on a single thread, Arc across threads.

The Rc versus RefCell

Rc<T> provides shared access and does not allow mutation through the Rc itself. RefCell<T> provides interior mutability with runtime borrow checking. The two solve different problems, and they are often combined as Rc<RefCell<T>>.

The Rc versus Mutex

Mutex<T> provides mutable access across threads. It is the multithreaded counterpart of RefCell, and it is usually paired with Arc as Arc<Mutex<T>>.

The Rc versus a reference

A reference &T is a borrowed view with a lifetime. An Rc<T> is an owned shared value with no lifetime. The reference is cheaper but requires the borrow checker to prove the lifetime. The Rc is more flexible but has the counting overhead.

The pointer comparison

PointerOwnershipThread-safeMutableOverhead
Box<T>SingleIf T: SendIf T: MutOne allocation
Rc<T>SharedNoNoReference count
Arc<T>SharedYesNoAtomic count
RefCell<T>SingleNoYesBorrow flag
Mutex<T>SingleYesYesLock
&TBorrowedIf T: SyncNoNone

The Rc<RefCell> combination

The Rc<RefCell<T>> combination is the standard pattern for shared mutable state on a single thread. The Rc provides the shared ownership, and the RefCell provides the runtime-checked mutation.

use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let counter = Rc::new(RefCell::new(0));
    let a = Rc::clone(&counter);
    let b = Rc::clone(&counter);

    *a.borrow_mut() += 1;
    *b.borrow_mut() += 1;

    println!("{}", counter.borrow());  // 2
}

The borrow rules are checked at runtime. A double mutable borrow panics, which is the trade-off for the flexibility.

The Arc<Mutex> combination

The Arc<Mutex<T>> combination is the multithreaded counterpart.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            *c.lock().unwrap() += 1;
        }));
    }

    for h in handles {
        h.join().unwrap();
    }

    println!("{}", counter.lock().unwrap());  // 10
}

The Arc provides the shared ownership, and the Mutex provides the thread-safe mutation.

The performance consideration

Rc<T> is cheap but not free. Each clone increments the count, and each drop decrements it. The count is a memory operation, and the value is on the heap. For a hot loop, the cost is measurable, and a reference is faster.

The lifetime consideration

Rc<T> does not have a lifetime parameter. The value lives as long as any Rc points to it, and the compiler does not track the lifetime. The runtime count does the work that the borrow checker would do at compile time.


Complete Example Session

// ============================================
// PART 1: THE BASIC RC
// ============================================

use std::rc::Rc;

fn main() {
    let a = Rc::new(5);
    let b = Rc::clone(&a);
    println!("{}", Rc::strong_count(&a));  // 2
    println!("{}", *a);  // 5
}

// ============================================
// PART 2: THE COUNT
// ============================================

fn main() {
    let a = Rc::new(5);
    assert_eq!(Rc::strong_count(&a), 1);
    let b = Rc::clone(&a);
    assert_eq!(Rc::strong_count(&a), 2);
    drop(b);
    assert_eq!(Rc::strong_count(&a), 1);
}

// ============================================
// PART 3: THE SHARED CONFIG
// ============================================

struct Config {
    name: String,
    version: u32,
}

fn main() {
    let config = Rc::new(Config {
        name: "app".to_string(),
        version: 1,
    });

    let a = Rc::clone(&config);
    let b = Rc::clone(&config);

    println!("{} v{}", a.name, b.version);
}

// ============================================
// PART 4: THE LINKED LIST
// ============================================

struct Node {
    value: i32,
    next: Option<Rc<Node>>,
}

fn main() {
    let second = Rc::new(Node { value: 2, next: None });
    let first = Rc::new(Node { value: 1, next: Some(Rc::clone(&second)) });

    println!("{}", first.next.as_ref().unwrap().value);  // 2
}

// ============================================
// PART 5: THE RC WITH REFCELL
// ============================================

use std::cell::RefCell;

fn main() {
    let value = Rc::new(RefCell::new(5));
    let a = Rc::clone(&value);
    let b = Rc::clone(&value);

    *a.borrow_mut() += 1;
    *b.borrow_mut() += 1;

    println!("{}", value.borrow());  // 7
}

// ============================================
// PART 6: THE TREE
// ============================================

struct TreeNode {
    value: i32,
    children: Vec<Rc<TreeNode>>,
}

fn main() {
    let leaf = Rc::new(TreeNode { value: 1, children: vec![] });
    let root = Rc::new(TreeNode {
        value: 0,
        children: vec![Rc::clone(&leaf)],
    });

    println!("{}", root.children[0].value);
}

// ============================================
// PART 7: THE CYCLE LEAK
// ============================================

struct CyclicNode {
    value: i32,
    next: RefCell<Option<Rc<CyclicNode>>>,
}

fn main() {
    let a = Rc::new(CyclicNode { value: 1, next: RefCell::new(None) });
    let b = Rc::new(CyclicNode { value: 2, next: RefCell::new(Some(Rc::clone(&a))) });
    *a.next.borrow_mut() = Some(Rc::clone(&b));
    // The cycle leaks. Neither a nor b is dropped.
}

// ============================================
// PART 8: THE WEAK REFERENCE
// ============================================

use std::rc::Weak;

struct ParentNode {
    value: i32,
    parent: RefCell<Weak<ParentNode>>,
    children: RefCell<Vec<Rc<ParentNode>>>,
}

fn main() {
    let parent = Rc::new(ParentNode {
        value: 1,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    let child = Rc::new(ParentNode {
        value: 2,
        parent: RefCell::new(Rc::downgrade(&parent)),
        children: RefCell::new(vec![]),
    });

    parent.children.borrow_mut().push(Rc::clone(&child));

    if let Some(p) = child.parent.borrow().upgrade() {
        println!("{}", p.value);  // 1
    }
}

// ============================================
// PART 9: THE TRY_UNWRAP
// ============================================

fn main() {
    let a = Rc::new(5);
    match Rc::try_unwrap(a) {
        Ok(value) => println!("{}", value),  // 5
        Err(_) => println!("Still shared"),
    }
}

// ============================================
// PART 10: THE COMPARISON
// ============================================

use std::sync::Arc;
use std::sync::Mutex;

fn single_thread() {
    let data = Rc::new(RefCell::new(0));
    *data.borrow_mut() += 1;
}

fn multi_thread() {
    let data = Arc::new(Mutex::new(0));
    *data.lock().unwrap() += 1;
}

The ten parts showed the basic Rc, the count, the shared config, the linked list, the Rc<RefCell<T>> combination, the tree, the cycle leak, the Weak reference, the try_unwrap, and the comparison with Arc<Mutex<T>>.


Quick Reference

The Rc API

MethodPurpose
Rc::new(value)Create a new Rc
Rc::clone(&rc)Increment the count
Rc::strong_count(&rc)The strong count
Rc::weak_count(&rc)The weak count
Rc::try_unwrap(rc)Extract if unique
Rc::ptr_eq(&a, &b)Same allocation
Rc::into_raw(rc)Consume to a raw pointer
Rc::from_raw(p)Reconstruct from a raw pointer
Rc::downgrade(&rc)Create a Weak

The Weak API

MethodPurpose
Weak::new()Create an empty Weak
Rc::downgrade(&rc)Create a Weak from an Rc
weak.upgrade()Return Option<Rc<T>>
weak.strong_count()The strong count
weak.weak_count()The weak count

The Pointer Comparison

PointerOwnershipThread-safeMutable
Box<T>SingleIf T: SendIf T: Mut
Rc<T>SharedNoNo
Arc<T>SharedYesNo
RefCell<T>SingleNoYes
Mutex<T>SingleYesYes

The Common Combinations

CombinationUse Case
Rc<T>Shared immutable
Rc<RefCell<T>>Shared mutable, single thread
Rc<Weak<T>>Back references, cycle-safe
Arc<Mutex<T>>Shared mutable, multi thread
Arc<RwLock<T>>Shared read-mostly, multi thread

The Cycle Indicators

SymptomCauseFix
Memory never freedCycle of strong refsUse Weak for back edges
Count never reaches zeroCycleBreak the cycle
Panic on borrow_mutDouble mutable borrowShorten the borrow

Best Practices

✅ Do This:

// Use Rc for shared immutable data.
let config = Rc::new(Config { ... });  // ✅

// Use Rc::clone for cloning.
let b = Rc::clone(&a);  // ✅

// Use Weak for back references.
parent: RefCell::new(Rc::downgrade(&parent))  // ✅

// Use Rc<RefCell<T>> for shared mutable state.
let counter = Rc::new(RefCell::new(0));  // ✅

// Use Arc for multithreaded sharing.
let data = Arc::new(Mutex::new(0));  // ✅

// Check the count when debugging leaks.
println!("{}", Rc::strong_count(&a));  // ✅

// Use upgrade() to check if a Weak is still alive.
if let Some(p) = weak.upgrade() { }  // ✅

❌ Don’t Do This:

// Don't use Rc across threads.
let a = Rc::new(5);
thread::spawn(move || { *a });  // ❌ Rc is not Send

// Don't create a cycle of strong references.
a.next = Some(Rc::clone(&b));
b.next = Some(Rc::clone(&a));  // ❌ Leaks

// Don't use Rc when a reference suffices.
fn process(data: Rc<Vec<i32>>) { }  // ❌ Use &[i32]

// Don't clone the inner value.
let b = (*a).clone();  // ❌ Clones the Vec, not the Rc

// Don't hold a RefCell borrow across a function call.
let r = cell.borrow();
other_function();  // ❌ May panic on borrow_mut

// Don't use Rc for a single-owner value.
let x = Rc::new(5);  // ⚠️ Box is simpler

Common Pitfalls

PitfallWhy It HappensFix
Memory leakCycle of RcUse Weak for back edges
Not SendRc used across threadsUse Arc
Panic on borrowDouble borrow_mutShorten the borrow
Count not zeroCycle or forgotten refFind the cycle
Slow cloneCloning inner valueUse Rc::clone
Reference cycleParent and child point to each otherWeak for one direction
try_unwrap failsOther Rc values existDrop them first

Real-World Examples

1. Basic Rc

let a = Rc::new(5);
let b = Rc::clone(&a);

2. Shared Config

let config = Rc::new(Config { name: "app".into() });

3. Rc in a Struct

struct Node { next: Option<Rc<Node>> }

4. Rc<RefCell>

let value = Rc::new(RefCell::new(5));
*value.borrow_mut() += 1;

5. Tree

children: Vec<Rc<TreeNode>>

6. Weak Reference

parent: RefCell::new(Weak::new())

7. Downgrade

Rc::downgrade(&parent)

8. Upgrade

if let Some(p) = weak.upgrade() { }

9. Strong Count

println!("{}", Rc::strong_count(&a));

10. Arc<Mutex>

let data = Arc::new(Mutex::new(0));

Visual

The Rc Structure

┌────────────────────────────────────────────────────────────┐
│  THE Rc STRUCTURE                                          │
│                                                            │
│  let a = Rc::new(5);                                       │
│  let b = Rc::clone(&a);                                    │
│  let c = Rc::clone(&a);                                    │
│                                                            │
│  STACK                          HEAP                       │
│  ┌──────────┐                   ┌──────────────────────┐  │
│  │ a: ptr ──┼──────────────────▶│ strong: 3            │  │
│  ├──────────┤                   │ weak: 0              │  │
│  │ b: ptr ──┼──────────────────▶│ value: 5             │  │
│  ├──────────┤                   └──────────────────────┘  │
│  │ c: ptr ──┼──────────────────▶                         │
│  └──────────┘                                             │
│                                                            │
│  The three Rc values share the same heap allocation.       │
│  The strong count is 3.                                    │
│  The value is dropped when the count reaches 0.            │
└────────────────────────────────────────────────────────────┘

The Clone and Drop

┌────────────────────────────────────────────────────────────┐
│  CLONE AND DROP                                            │
│                                                            │
│  let a = Rc::new(5);       strong = 1                     │
│  let b = Rc::clone(&a);    strong = 2                     │
│  let c = Rc::clone(&a);    strong = 3                     │
│                                                            │
│  drop(b);                  strong = 2                     │
│  drop(c);                  strong = 1                     │
│  drop(a);                  strong = 0 → value dropped     │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  Each clone increments the count.                 │     │
│  │  Each drop decrements the count.                  │     │
│  │  The value is dropped when the count reaches 0.   │     │
│  └──────────────────────────────────────────────────┘     │
└────────────────────────────────────────────────────────────┘

The Cycle Leak

┌────────────────────────────────────────────────────────────┐
│  THE CYCLE LEAK                                            │
│                                                            │
│  let a = Rc::new(Node { next: None });                     │
│  let b = Rc::new(Node { next: Some(Rc::clone(&a)) });      │
│  *a.next.borrow_mut() = Some(Rc::clone(&b));               │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  a ──────────────▶ b                              │     │
│  │  ▲                 │                              │     │
│  │  │                 │                              │     │
│  │  └─────────────────┘                              │     │
│  │                                                   │     │
│  │  a's count: 2 (b.next and a)                      │     │
│  │  b's count: 2 (a.next and b)                      │     │
│  │                                                   │     │
│  │  When the scope ends, a's count drops to 1,       │     │
│  │  and b's count drops to 1. Neither reaches 0.     │     │
│  │  The cycle leaks.                                 │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  FIX: use Weak for one direction.                          │
└────────────────────────────────────────────────────────────┘

The Rc<RefCell> Pattern

┌────────────────────────────────────────────────────────────┐
│  Rc<RefCell<T>> PATTERN                                    │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  Rc<T>                                            │     │
│  │    Shared ownership                               │     │
│  │    Multiple owners                                │     │
│  │    Immutable access                               │     │
│  └────────────────────┬─────────────────────────────┘     │
│                       │                                    │
│                       │  combined with                    │
│                       v                                    │
│  ┌──────────────────────────────────────────────────┐     │
│  │  RefCell<T>                                       │     │
│  │    Interior mutability                            │     │
│  │    Runtime borrow checking                        │     │
│  │    Single-threaded                                │     │
│  └────────────────────┬─────────────────────────────┘     │
│                       │                                    │
│                       v                                    │
│  ┌──────────────────────────────────────────────────┐     │
│  │  Rc<RefCell<T>>                                   │     │
│  │    Shared mutable state                           │     │
│  │    Single-threaded                                │     │
│  │    Panics on double mutable borrow                │     │
│  └──────────────────────────────────────────────────┘     │
└────────────────────────────────────────────────────────────┘

Summary

ItemValue
Rc<T>Shared ownership on a single thread
Rc::newCreate a new Rc
Rc::cloneIncrement the count
Rc::strong_countThe number of owners
Rc::weak_countThe number of weak refs
Rc::try_unwrapExtract if unique
Rc::ptr_eqSame allocation
Weak<T>A non-owning reference
Rc<RefCell<T>>Shared mutable state
Arc<T>The thread-safe counterpart

Key takeaways:

  • Rc<T> provides shared ownership on a single thread. The reference count tracks how many owners a value has, and the value is dropped when the count reaches zero. The count is not atomic, so the type is not thread-safe.
  • Rc::clone increments the count; drop decrements it. The clone is cheap because it copies a pointer, not the value. The value is shared, not duplicated.
  • The deref makes Rc<T> transparent. Method calls and field accesses reach the inner value, so the Rc looks like the value it wraps.
  • A cycle of Rc values leaks. Two Rc values pointing to each other keep each other’s count above zero. The Weak<T> type breaks the cycle by holding a non-owning reference.
  • Rc<RefCell<T>> is the shared-mutable pattern. The Rc provides the shared ownership, and the RefCell provides the runtime-checked mutation. The combination is single-threaded and panics on a double mutable borrow.
  • Arc<T> is the thread-safe counterpart. It uses an atomic count, which is safe across threads but slower. The rule is: Rc on a single thread, Arc across threads.
  • Rc::try_unwrap extracts the value if the count is 1. The function is used when the value is no longer shared and the inner value is needed by value.
  • Rc::ptr_eq compares the allocation, not the value. The == operator compares the inner values, which is a different check.
  • The Weak reference does not contribute to the strong count. The upgrade method returns Some(Rc<T>) if the value is still alive, or None if it has been dropped.

Remember: Rc<T> is the smart pointer for shared ownership on a single thread. It keeps a count of how many owners a value has, and it drops the value when the count reaches zero. The clone is cheap because it copies a pointer, and the deref makes the Rc transparent. The Rc<RefCell<T>> combination provides shared mutable state, and the Weak<T> type breaks cycles. The type is not thread-safe, so it cannot be sent across threads; the Arc<T> type is the multithreaded counterpart. The result is shared ownership without the borrow checker’s single-owner rule, and automatic cleanup without a garbage collector.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!