Skip to main content
Rust Programming
CHAPTER 19 Beginner

Smart Pointers in Rust

Updated: May 18, 2026
5 min read

# CHAPTER 19

Smart Pointers in Rust

1. Chapter Introduction

We know that Rust enforces strict rules: only one owner per value, and values must have known sizes. But what if we are building a Graph or a Tree, where multiple nodes need to share ownership of a child node? Or what if we have a massive, recursive data structure of unknown size? To handle these complex architectures, Rust provides Smart Pointers.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Understand what a Smart Pointer is (Deref and Drop traits).
  • Use Box<T> to force data onto the Heap.
  • Use Rc<T> to allow multiple owners of the same data.
  • Use RefCell<T> for "Interior Mutability."

3. What is a Smart Pointer?

A standard pointer (like a reference &) just points to memory. A Smart Pointer is a data structure that acts like a pointer but has additional metadata and capabilities (like automatically managing memory cleanup). In fact, String and Vec<T> are technically Smart Pointers!

They are usually implemented using structs that possess the Deref trait (allowing them to behave like standard references) and the Drop trait (allowing them to execute cleanup code when they go out of scope).

4. Box<T> (Allocating on the Heap)

Box<T> is the simplest smart pointer. Its only job is to force data to be stored on the Heap instead of the Stack.

When to use it:

  1. 1. When you have a massive amount of data and want to avoid copying it on the Stack.
  1. 2. When you have a Recursive Type (a type that contains itself).

Because the compiler needs to know exactly how many bytes a struct takes up, a recursive struct (like a Linked List node) has an infinite size! Box<T> fixes this because a Box is just a pointer, and pointers have a known, fixed size.

rust
123456789101112131415
// Recursive enum representing a Linked List
enum List {
    Cons(i32, Box<List>), // Box ensures the compiler knows the size!
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Nil))));
    
    // Simple Box usage for an integer
    let b = Box::new(5);
    println!("b = {}", b);
}

5. Rc<T> (Reference Counted Smart Pointer)

Rule #2 of Ownership is "There can only be ONE owner". But in a graph data structure, a single node might be pointed to by three other nodes. If the first node drops, the child drops, leaving the other two pointing to dead memory.

Rc<T> (Reference Counted) allows multiple owners. It keeps a secret counter of how many variables own the data. When an owner goes out of scope, the counter decreases. The heap memory is only deleted when the counter hits zero.

rust
1234567891011
use std::rc::Rc;

fn main() {
    let shared_data = Rc::new(String::from("Shared String"));
    
    // Rc::clone doesn't copy the string data! It just increments the counter.
    let owner1 = Rc::clone(&shared_data);
    let owner2 = Rc::clone(&shared_data);
    
    println!("Reference count: {}", Rc::strong_count(&shared_data)); // Prints 3
}

*(Warning: Rc<T> is strictly for single-threaded scenarios. For multi-threading, you must use Arc<T> which we will cover in Chapter 22).*

6. RefCell<T> (Interior Mutability)

Borrowing rules say you cannot mutate data if you have immutable references to it. RefCell<T> is an advanced tool that bypasses the compile-time borrow checker, enforcing the borrowing rules at *runtime* instead. It allows you to mutate data even when there are immutable references to the RefCell itself! This is called "Interior Mutability".
rust
12345678910
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    // We borrow it mutably at runtime
    *data.borrow_mut() += 10;
    
    println!("Data is now: {}", data.borrow()); // Prints 15
}

*(Warning: If you break the borrowing rules with RefCell at runtime (e.g., calling borrow_mut() twice in the same scope), the program will Panic and crash!)*

7. Common Mistakes

  • Using Rc<T> across Threads: Passing an Rc into a thread will cause a compiler error because the internal counter is not thread-safe.
  • Reference Cycles (Memory Leaks): If you use Rc and RefCell to create a cycle (Node A points to Node B, and Node B points back to Node A), their reference counters will never reach zero. This creates a memory leak in Rust!

8. Best Practices

  • Default to standard references: Do not use Smart Pointers unless standard ownership and borrowing absolutely cannot solve your architectural problem. They introduce slight overhead and complexity.

9. Exercises

  1. 1. Create a variable storing a massive float 3.14159 directly on the heap using Box::new().
  1. 2. Print the value.

10. MCQs with Answers

Question 1

What characterizes a Smart Pointer in Rust?

Question 2

What is the primary purpose of Box<T>?

Question 3

Why are recursive data types (like a Linked List node) illegal without Box<T>?

Question 4

What Ownership rule does Rc<T> bypass?

Question 5

How does Rc<T> know when it is safe to delete the Heap memory?

Question 6

What does Rc::clone(&data) actually do?

Q7. Is Rc<T> safe to use across multiple concurrent Threads? a) Yes b) No Answer: b) No.
Question 8

What compiler rule does RefCell<T> bypass?

Question 9

What happens if you violate borrowing rules using RefCell at runtime?

Q10. Can you create a Memory Leak in Rust? a) No, Rust is perfect b) Yes, by creating Reference Cycles using Rc and RefCell where counters never reach zero Answer: b) Yes, by creating Reference Cycles.

11. Interview Questions

  • Q: Describe a scenario where you would use Box<T>.
  • Q: Explain "Interior Mutability" and how RefCell achieves it.
  • Q: How does Rc<T> manage memory without a global Garbage Collector?

12. Summary

Smart Pointers unlock complex data structures in Rust. Box solves recursive sizing issues. Rc allows multiple owners for graph-like architectures. RefCell provides an escape hatch from the strict compile-time borrow checker for dynamic mutability. Together, they provide the flexibility of a Garbage Collected language while maintaining the performance of manual memory management.

13. Next Chapter Recommendation

Now that we have mastered memory, it is time to build real systems. In Chapter 20: File Handling in Rust, we will interact directly with the Operating System to read and write files permanently to the hard drive.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·