DEV Community

Subesh Yadav
Subesh Yadav

Posted on

Day 30 of #100DaysOfRust: Reference Counting with Rc<T>

Today I explored Rc, the Reference Counted Smart Pointer in Rust. This is where ownership gets interesting—sometimes a value might need multiple owners, and Rc provides exactly that functionality.


🔄 Why Rc?

Normally, ownership in Rust is strict: only one owner exists for any given value. But in real-world cases, multiple parts of a program may need to share access. For example, in graph structures, multiple edges may point to the same node. That node shouldn’t be deallocated until all edges stop pointing to it.

Rc<T> keeps track of the number of references to a value. Once all references are gone, the value is safely cleaned up.

👉 Think of Rc like a TV in a family room: everyone can watch while they’re in the room, and only when the last person leaves is the TV turned off.

⚠️ Rc works only in single-threaded programs. For multithreading, Rust provides Arc<T> (Atomic Reference Counting).


📝 Example: Shared Ownership in Lists

Let’s revisit the cons list example. If we use Box<T> for ownership, we hit a problem:

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
    let b = Cons(3, Box::new(a));
    let c = Cons(4, Box::new(a)); // ❌ Error: a was moved
}
Enter fullscreen mode Exit fullscreen mode

This fails because a is moved into b, so it can’t also be used in c.


✅ Fixing with Rc

By using Rc, we enable multiple ownership:

use std::rc::Rc;

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}
Enter fullscreen mode Exit fullscreen mode

Here, Rc::clone(&a) increments the reference count instead of deeply copying the data. Now a, b, and c all share ownership.


📊 Tracking Reference Counts

We can observe how reference counts change with Rc::strong_count:

use std::rc::Rc;

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));
    let b = Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
Enter fullscreen mode Exit fullscreen mode

Output:

count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2
Enter fullscreen mode Exit fullscreen mode

The reference count goes up with every Rc::clone and down when a reference goes out of scope. When it finally reaches 0, the data is deallocated.


🚫 Why Rc Only Allows Immutable References

Rc<T> lets you share ownership via immutable references. If multiple owners could also mutate the data, Rust’s borrowing rules would be violated (e.g., multiple mutable borrows at once).

But sometimes, we do want shared mutable access. That’s where the interior mutability pattern comes in, using RefCell<T> alongside Rc<T>. I’ll explore this in the next session.


🧠 Summary

  • Rc<T> enables multiple ownership through reference counting.
  • Cloning with Rc::clone increments the count instead of deep-copying.
  • When the last reference is dropped, the data is freed.
  • Best for single-threaded scenarios where shared ownership is needed.
  • For mutation, combine with RefCell<T> (coming up next!).

🚀 That wraps up Day 30 of #100DaysOfRust! Today I learned how Rc<T> provides shared ownership and safe cleanup in Rust.

Tomorrow: Interior Mutability with RefCell.

Top comments (0)