Thuta Learning
Rust
AdvancedProgrammingbeginner

Smart Pointers — Box, Rc, RefCell

What you'll walk away with

  • Explain the core ideas behind Smart Pointers — Box, Rc, RefCell
  • Run the sample Rust code and verify its output
  • Apply the technique correctly to the Tutorial Platform and production scenarios

Build the mental model

Strictly following Lesson 6's ownership rule ("each value has exactly one owner") makes some real-world data structures — linked lists, trees, graphs, shared caches — genuinely hard to express; the three smart pointers here are escape hatches for that limitation. `Box<T>` is the simplest: it allocates a value on the heap while still following the ownership rules, useful for recursive types the compiler can't size at compile time (like a tree-node struct that needs a field of its own type) — `Box`'s fixed pointer size gives the compiler a known compile-time size. `Rc<T>` (Reference Counted) relaxes Lesson 6's "exactly one owner" rule — it lets multiple owners share the same value, tracking an internal count that drops the value only once it reaches zero — and it's safe only in single-threaded contexts. `RefCell<T>` moves the borrow checker's compile-time check to a runtime check instead — an "interior mutability" pattern that lets you mutate a value's inner content via `.borrow_mut()` even while only holding an immutable reference (`&self`), enforcing Lesson 7's borrow rule at runtime instead of compile time — violating it causes a runtime panic instead of a compile error. Combining `Rc<RefCell<T>>` lets multiple owners share and mutate a value in a single-threaded context, a pattern you'll meet again as the multi-threaded equivalent, `Arc<Mutex<T>>`, in Lesson 18.

Connect it to a real scenario

To model the content analyzer's lesson tree structure (a topic tree of sub-topics inside a chapter), use `Box`: `struct TopicNode { name: String, children: Vec<Box<TopicNode>> }` — wrapping the recursive struct in `Box` gives the compiler a known compile-time size. To share a tag cache across multiple analyzer functions, use `Rc<Vec<String>>` — every `.clone()` call bumps only the reference count instead of deep-copying the underlying `Vec`, keeping it memory-efficient. To share and update a running-total counter across multiple functions, use `Rc<RefCell<u32>>` — mutate it with `counter.borrow_mut()`, getting Lesson 7's borrow rule enforced at runtime instead.

Try the working example

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

struct TopicNode {
    name: String,
    children: Vec<Box<TopicNode>>,
}

fn main() {
    let leaf = Box::new(TopicNode { name: String::from("Ownership"), children: vec![] });
    let root = TopicNode { name: String::from("Basics"), children: vec![leaf] };
    println!("{} -> {}", root.name, root.children[0].name);

    let counter = Rc::new(RefCell::new(0));
    let counter_clone = Rc::clone(&counter);
    *counter_clone.borrow_mut() += 5;
    println!("shared counter: {}", counter.borrow());
    println!("owners: {}", Rc::strong_count(&counter));
}
You should see
You print "Basics -> Ownership", "shared counter: 5", and "owners: 2".

5-minute try-it

Use `Rc<RefCell<Vec<String>>>` to create a shared tag list — clone it to create two owners, push a tag from one owner (`.borrow_mut().push(...)`), and check whether the change is visible from the other owner.

One important caution

Trying to use `Rc<T>` across threads — `Rc`'s reference counter isn't thread-safe, and the compiler blocks it with a compile error; the multi-threaded equivalent `Arc<T>` (atomic reference count) from Lesson 18 is needed instead.

Calling `.borrow_mut()` on a `RefCell<T>` again while a borrow from it is already active in the same scope — instead of a compile error, this triggers an "already borrowed" runtime panic, since RefCell's safety check only happens at runtime and can't catch this mistake ahead of time.

The Rust Programming Language — Smart PointersRust

Easy traps

  • Trying to use `Rc<T>` across threads — `Rc`'s reference counter isn't thread-safe, and the compiler blocks it with a compile error; the multi-threaded equivalent `Arc<T>` (atomic reference count) from Lesson 18 is needed instead.
  • Calling `.borrow_mut()` on a `RefCell<T>` again while a borrow from it is already active in the same scope — instead of a compile error, this triggers an "already borrowed" runtime panic, since RefCell's safety check only happens at runtime and can't catch this mistake ahead of time.
  • Validate sample code in a local or test environment before applying it to a production system.

Exercise

Use `Rc<RefCell<Vec<String>>>` to create a shared tag list — clone it to create two owners, push a tag from one owner (`.borrow_mut().push(...)`), and check whether the change is visible from the other owner.

You'll know it worked when: You print "Basics -> Ownership", "shared counter: 5", and "owners: 2".

Smart Pointers — Box, Rc, RefCell | Thuta Learning