Rc and Interior Mutability

Back

Loading concept...

🎈 Smart Pointers: Sharing & Changing Without Fighting

Imagine you have a favorite toy. What if many friends could play with it at the same time? And what if you could change what’s inside a locked box without opening it? That’s what Rust’s smart pointers help us do!


🧸 The Sharing Toy: Meet Rc<T>

What’s the Problem?

In Rust, every toy (value) has one owner. When the owner is done, the toy is thrown away.

But wait—what if multiple kids want to share the same toy?

Enter Rc<T> — the Reference Counted smart pointer!

The Magic Clipboard Analogy 📋

Think of Rc like a library book with a sign-out sheet.

  • When you borrow the book, you write your name on the sheet.
  • The librarian counts how many names are on the sheet.
  • The book stays on the shelf until everyone crosses off their name.
  • When the last name is crossed off → the book can be put away.
use std::rc::Rc;

// Create a shared toy
let toy = Rc::new("Teddy Bear");

// Clone creates another "name on the sheet"
let friend1 = Rc::clone(&toy);
let friend2 = Rc::clone(&toy);

// How many friends are sharing?
println!("Count: {}", Rc::strong_count(&toy));
// Output: Count: 3

📊 Reference Counting in Action

graph TD A["Rc - Teddy Bear"] --> B["Owner 1"] A --> C["Owner 2"] A --> D["Owner 3"] E["Count = 3"] --> A

Every Rc::clone() increases the count. When each clone goes away, the count decreases. When count hits zero, the toy is dropped.


🔗 Weak References: The Gentle Observer

The Problem with Too Much Sharing

What if two toys hold onto each other?

Toy A says: "I own Toy B!"
Toy B says: "I own Toy A!"

Neither can be dropped. They’re stuck forever. This is a memory leak.

The Solution: Weak<T>

A Weak reference is like a sticky note that says “I know where that toy is” but doesn’t count as an owner.

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

let strong = Rc::new("Ball");
let weak: Weak<_> = Rc::downgrade(&strong);

// Weak doesn't increase the count!
println!("Strong: {}", Rc::strong_count(&strong));
// Output: Strong: 1

// To use weak, try to upgrade it
if let Some(ball) = weak.upgrade() {
    println!("Ball is still here: {}", ball);
}
graph TD A["Rc - Strong Reference"] -->|counts| B["Data"] C["Weak Reference"] -.->|observes| B D["Count = 1"] --> B

Strong = counts toward keeping alive. Weak = just watching, no vote.


⚠️ Memory Leak with Rc Cycles

The Danger Zone

When two Rc pointers point at each other, neither can reach zero.

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

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

let a = Rc::new(RefCell::new(Node { next: None }));
let b = Rc::new(RefCell::new(Node { next: None }));

// Create a cycle! 🔄
a.borrow_mut().next = Some(Rc::clone(&b));
b.borrow_mut().next = Some(Rc::clone(&a));

// Now a → b → a → b → ... forever!
// Memory leak! 💧

Breaking the Cycle

Use Weak for one direction:

struct Node {
    next: Option<Rc<RefCell<Node>>>,
    prev: Option<Weak<RefCell<Node>>>, // Weak!
}

Now prev doesn’t count, so the cycle can break.


🔓 Interior Mutability: Changing the Unchangeable

The Locked Box Analogy

Rust says: “If I give you a shared reference (&T), you can look but not touch.”

But sometimes we need to change something inside!

Interior Mutability = a locked box where you have the key.

From the outside, it looks immutable. But inside, you can change things.


📦 RefCell: Runtime Borrowing Rules

What Is RefCell?

RefCell<T> is like a library book with a checkout system.

  • One writer at a time (exclusive)
  • Many readers at a time (but no writer)
  • Rules checked at runtime, not compile time
use std::cell::RefCell;

let book = RefCell::new(5);

// Borrow for reading
let reader1 = book.borrow();
let reader2 = book.borrow();
println!("Value: {} {}", *reader1, *reader2);

// Must drop readers before writing!
drop(reader1);
drop(reader2);

// Borrow for writing
*book.borrow_mut() = 10;
println!("New value: {}", book.borrow());

⚠️ Runtime Panic!

Break the rules? Program panics at runtime:

let data = RefCell::new(42);

let reader = data.borrow();
let writer = data.borrow_mut(); // 💥 PANIC!
graph TD A["RefCell"] --> B{Borrow Request} B -->|borrow| C["Reading ✓"] B -->|borrow_mut| D["Writing ✓"] C --> E{More borrows?} E -->|borrow| F["OK ✓"] E -->|borrow_mut| G["PANIC! 💥"]

⚛️ Cell: Simple Value Swapping

What Is Cell?

Cell<T> is simpler than RefCell. No borrowing—just get and set.

Works for types that can be copied (like numbers).

use std::cell::Cell;

let counter = Cell::new(0);

// Get the value (copies it out)
let current = counter.get();
println!("Current: {}", current);

// Set a new value
counter.set(current + 1);
println!("After: {}", counter.get());

Cell vs RefCell

Feature Cell RefCell
Works with Copy types Any type
Borrowing No Yes
Can panic? No Yes
Use case Simple counters Complex data

🐄 Cow: Clone on Write

The Lazy Copier

Cow stands for Clone On Write.

Imagine a document:

  • If you only read it, use the original.
  • If you want to edit, make a copy first.

This saves memory when you often don’t need to modify!

use std::borrow::Cow;

fn maybe_uppercase(input: &str) -> Cow<str> {
    if input.contains("!") {
        // Need to modify → make owned copy
        Cow::Owned(input.to_uppercase())
    } else {
        // Just reading → borrow original
        Cow::Borrowed(input)
    }
}

let a = maybe_uppercase("hello");
// a is Borrowed → no copy made ✓

let b = maybe_uppercase("hello!");
// b is Owned → copy made, now uppercase
graph TD A["Input Data"] --> B{Need to modify?} B -->|No| C["Cow::Borrowed"] B -->|Yes| D["Cow::Owned"] C --> E["Uses original - fast!"] D --> F["Makes a copy first"]

Why Use Cow?

  • Performance: Avoid unnecessary copies
  • Flexibility: Same type whether borrowed or owned
  • Common in: String processing, data transformation

🎯 Quick Summary

Pointer Purpose Key Idea
Rc<T> Share ownership Multiple owners, count refs
Weak<T> Observe without owning Prevents cycles
RefCell<T> Mutate shared data Runtime borrow checks
Cell<T> Simple value swaps Get/Set, no borrowing
Cow<T> Lazy cloning Clone only when needed

🌟 The Complete Picture

graph TD A["Smart Pointers"] --> B["Rc - Sharing"] A --> C["Interior Mutability"] A --> D["Cow - Efficiency"] B --> E["Strong refs"] B --> F["Weak refs"] C --> G["RefCell"] C --> H["Cell"] E --> I["Counts ownership"] F --> J["Breaks cycles"] G --> K["Runtime borrowing"] H --> L["Simple get/set"]

💪 You Did It!

You now understand:

  • ✅ How Rc lets multiple owners share data
  • ✅ How Weak prevents memory leaks
  • ✅ Why cycles are dangerous
  • ✅ How RefCell bends borrowing rules
  • ✅ When to use simple Cell
  • ✅ How Cow saves memory

These tools give you superpowers in Rust. You can share data safely, mutate when needed, and write efficient code.

Go build something amazing! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.