Skip to content
DocsRust LearningadvancedSmart Pointers
Chapter 13 of 19·advanced·10 min read

Smart Pointers

Con Trỏ Thông Minh

Box, Rc, Arc, and RefCell

Hover or tap any paragraph to see Vietnamese translation

What Are Smart Pointers

Smart pointers are data structures that act like pointers but also have additional metadata and capabilities for memory management. Unlike regular references that only borrow data, smart pointers usually own the data they point to.

Rust has several built-in smart pointers in the standard library: Box<T>, Rc<T>, Arc<T>, RefCell<T>, and more. Each solves different problems related to memory ownership and concurrency.

Box<T> for Heap Allocation

Box<T> is the most straightforward smart pointer in Rust. It lets you store data on the heap rather than the stack. The stack holds only the pointer to the heap data. When a Box goes out of scope, the heap data is also deallocated.

box_basics.rs
1fn main() {2    // Store an i32 on the heap3    let b = Box::new(5);4    println!("b = {}", b);  // b = 556    // Box is dereferenced automatically7    let x = *b + 1;  // x = 689    // b is dropped here, heap memory freed10}1112// Boxes are useful for large data you want to move13// without copying the whole structure14fn process(data: Box<[u8; 1_000_000]>) {15    // Only a pointer (8 bytes) is moved, not 1MB of data16    println!("First byte: {}", data[0]);17}

When to Use Box

There are three most common use cases for Box: when you have a type whose size cannot be determined at compile time, when you want to transfer ownership of large data without copying, and when you want to own a value you only care about as a trait implementor.

box_use_cases.rs
1// Use case 1: Recursive types (size unknown at compile time)2// This would cause a compile error WITHOUT Box:3// enum List { Cons(i32, List), Nil }45// Correct version using Box:6enum List {7    Cons(i32, Box<List>),8    Nil,9}1011fn main() {12    let list = List::Cons(

The Deref Trait and Deref Coercion

The Deref trait lets you customize the behavior of the dereference operator *. By implementing Deref, a smart pointer can be used just like a regular reference. Deref coercion is the automatic conversion of a smart pointer reference into a reference to the inner type.

deref_trait.rs
1use std::ops::Deref;23struct MyBox<T>(T);45impl<T> MyBox<T> {6    fn new(x: T) -> MyBox<T> {7        MyBox(x)8    }9}1011impl<T> Deref for MyBox<T> {12    type Target = T;
Tip
Deref coercion happens automatically when you pass a reference to a function whose parameter type does not match. Rust will call deref() as many times as needed to match the type.

The Drop Trait and Cleanup

The Drop trait lets you customize what happens when a value is about to go out of scope. It is similar to destructors in other languages. Rust automatically calls drop() when a variable goes out of scope, in the reverse order of declaration.

drop_trait.rs
1struct Resource {2    name: String,3}45impl Drop for Resource {6    fn drop(&mut self) {7        println!("Dropping resource: {}", self.name);8    }9}1011fn main() {12    let r1 = Resource { name: String::from("first") };
Warning
You cannot call the drop() method directly — use std::mem::drop() instead. Otherwise, Rust will give a compile error to prevent a double-free.

Rc<T> for Reference Counting

Rc<T> (Reference Counted) enables multiple owners of the same data within a single thread. It tracks the number of references to a value and frees the memory only when no references remain. Rc<T> is only for single-threaded scenarios.

rc_basics.rs
1use std::rc::Rc;23fn main() {4    let a = Rc::new(String::from("shared data"));56    println!("Reference count after creating a: {}", Rc::strong_count(&a));7    // Output: 189    let b = Rc::clone(&a);  // Increments count, does NOT deep copy10    println!("Reference count after cloning to b: {}", Rc::strong_count(&a));11    // Output: 212

Arc<T> for Atomic Reference Counting

Arc<T> (Atomically Reference Counted) is like Rc<T> but safe to use across multiple threads. The reference counting is done with atomic operations, ensuring thread safety. Arc has slightly higher performance cost than Rc.

arc_threads.rs
1use std::sync::Arc;2use std::thread;34fn main() {5    let data = Arc::new(vec![1, 2, 3, 4, 5]);67    let mut handles = vec![];89    for i in 0..3 {10        let data_clone = Arc::clone(&data);11        let handle = thread::spawn(move || {12            println!(
Tip
Use Rc<T> when you are working within a single thread — it is faster than Arc<T>. Only use Arc<T> when you need to share data across threads.

RefCell<T> and Interior Mutability

RefCell<T> allows you to mutate data even when there are immutable references to it — a concept called interior mutability. Unlike Box<T> and Rc<T> which enforce borrowing rules at compile time, RefCell<T> enforces them at runtime.

refcell_interior_mutability.rs
1use std::cell::RefCell;23fn main() {4    let data = RefCell::new(vec![1, 2, 3]);56    // Immutable borrow7    {8        let v = data.borrow();9        println!("Data: {:?}", v);10    } // immutable borrow released here1112    // Mutable borrow
Warning
If you violate borrowing rules with RefCell<T> (for example, two simultaneous borrow_mut() calls), the program will panic at runtime instead of giving a compile-time error.

The Rc<RefCell<T>> Pattern

Combining Rc<T> and RefCell<T> creates a value with multiple owners that also allows mutation. This is a common pattern when you need to share mutable data across multiple parts of a program without multithreading.

rc_refcell_pattern.rs
1use std::cell::RefCell;2use std::rc::Rc;34#[derive(Debug)]5struct Node {6    value: i32,7    children: Vec<Rc<RefCell<Node>>>,8}910impl Node {11    fn new(value: i32) -> Rc<RefCell<Node>> {12        Rc::new(RefCell::new(Node {

Weak<T> References

Weak<T> is a weak reference to data managed by an Rc<T>. Unlike Rc::clone(), Weak::clone() does not increment the strong count, meaning it does not prevent the data from being freed. You call upgrade() to get an Option<Rc<T>> from a Weak.

weak_references.rs
1use std::cell::RefCell;2use std::rc::{Rc, Weak};34#[derive(Debug)]5struct Node {6    value: i32,7    parent: RefCell<Weak<Node>>,8    children: RefCell<Vec<Rc<Node>>>,9}1011fn main() {12    let parent = Rc::new(Node {

Memory Leak Prevention

Although Rust prevents most memory errors, it is still possible to create memory leaks with Rc<T> through reference cycles. When two Rc values point to each other, neither strong count will ever reach 0 and the memory will never be freed.

memory_leak_prevention.rs
1use std::cell::RefCell;2use std::rc::Rc;34// BAD: Reference cycle causes memory leak5#[derive(Debug)]6enum BadList {7    Cons(i32, RefCell<Rc<BadList>>),8    Nil,9}1011// GOOD: Use Weak to break cycles12use std::rc::Weak;
Tip
General rule: use Rc::clone() (strong reference) for ownership relationships, and Rc::downgrade() (weak reference) for back-references or peer relationships to prevent reference cycles.

Smart Pointers Summary

  • Box<T>: Heap allocation, single owner, stack-stored pointer to heap data
  • Rc<T>: Reference counting, multiple owners, single-threaded only, immutable
  • Arc<T>: Like Rc but thread-safe via atomic counting
  • RefCell<T>: Interior mutability, borrow checking at runtime
  • Rc<RefCell<T>>: Multiple owners + mutable, single-threaded
  • Arc<Mutex<T>>: Multiple owners + mutable, multi-threaded
  • Weak<T>: Non-owning reference that does not prevent deallocation, used to break cycles

Key Takeaways

Điểm Chính

  • Box<T> allocates data on the heap with single ownershipBox<T> cấp phát dữ liệu trên heap với một chủ sở hữu
  • Rc<T> enables multiple owners via reference counting (single-threaded)Rc<T> cho phép nhiều chủ sở hữu qua đếm tham chiếu (đơn luồng)
  • RefCell<T> enforces borrowing rules at runtime instead of compile timeRefCell<T> thực thi quy tắc mượn tại thời điểm chạy thay vì biên dịch
  • Arc<T> is the thread-safe version of Rc<T> using atomic operationsArc<T> là phiên bản an toàn luồng của Rc<T> dùng thao tác nguyên tử

Practice

Test your understanding of this chapter

Quiz

What is the key difference between Rc<T> and Arc<T>?

Sự khác biệt chính giữa Rc<T> và Arc<T> là gì?

Quiz

What happens if you call borrow_mut() on a RefCell<T> while an active borrow_mut() already exists?

Điều gì xảy ra nếu bạn gọi borrow_mut() trên RefCell<T> khi đã có một borrow_mut() đang hoạt động?

True or False

You can call the drop() method directly on a value (e.g., value.drop()) to explicitly free it early.

Bạn có thể gọi trực tiếp phương thức drop() trên một giá trị (ví dụ: value.drop()) để giải phóng bộ nhớ sớm.

True or False

A Weak<T> reference increments the strong count of an Rc<T>, keeping the data alive as long as the Weak reference exists.

Một tham chiếu Weak<T> tăng strong count của Rc<T>, giữ dữ liệu còn sống miễn là tham chiếu Weak tồn tại.

Code Challenge

Downgrade an Rc to a Weak reference to break a reference cycle

Hạ cấp Rc thành tham chiếu Weak để phá chu trình tham chiếu

let strong = Rc::new(5);
let weak: Weak<i32> = Rc::();

Chapter Complete!

Great job! Keep the momentum going.

Your progress0 of 19 chapters read
← → to navigate chapters
Built: 4/8/2026, 12:01:11 PM