Rust ArcBoxRcRefCellRustSmart Pointers

Article & Tutorial

Rust Smart Pointers: Box, Rc, Arc, RefCell, Deref and Drop Explained

Understand Rust smart pointers including Box, Rc, Arc, RefCell, Deref, Drop, interior mutability and when each ownership model is appropriate.

Rust Smart Pointers: Box, Rc, Arc, RefCell, Deref and Drop Explained article illustration
Rust Smart Pointers: Box, Rc, Arc, RefCell, Deref and Drop Explained article illustration
Contents

Rust Learning Path — Chapter 19. Rust references are usually the simplest way to access data, but some ownership patterns need more. Smart pointers combine pointer-like behavior with metadata and ownership rules. This chapter explains Box, Rc, Arc, RefCell, Deref, Drop, and common combinations.

1. What makes a pointer “smart”?

Types such as String and Vec already behave like smart pointers: they own heap data and manage cleanup. The standard smart-pointer types give you more explicit control over ownership patterns.

2. Box: single ownership on the heap

bash
fn main() { let value = Box::new(42); println!("{}", value); }

A Box owns its value and frees it when the box goes out of scope.

3. Recursive data structures

A recursive type needs an indirection because the compiler must know its size:

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

4. Rc: multiple owners in one thread

bash
use std::rc::Rc; fn main() { let shared = Rc::new(String::from("config")); let a = Rc::clone(&shared); let b = Rc::clone(&shared); println!("owners = {}", Rc::strong_count(&shared)); println!("{a} {b}"); }

Rc uses reference counting and is intended for single-threaded shared ownership.

5. Arc: thread-safe reference counting

Arc is the atomic, thread-safe counterpart to Rc:

bash
use std::{sync::Arc, thread}; fn main() { let value = Arc::new(String::from("shared")); let other = Arc::clone(&value); thread::spawn(move || println!("{other}")) .join() .unwrap(); }

Arc gives shared ownership; it does not automatically make the inner value mutable.

6. RefCell and interior mutability

RefCell moves borrow checking from compile time to runtime for a single-threaded value:

bash
use std::cell::RefCell; fn main() { let retries = RefCell::new(3); *retries.borrow_mut() += 1; println!("{}", retries.borrow()); }

If you violate the borrowing rules at runtime, RefCell panics. Use it intentionally, not as a shortcut around design problems.

7. Rc

A common single-thread pattern combines shared ownership with controlled mutation:

bash
use std::{cell::RefCell, rc::Rc}; fn main() { let counter = Rc::new(RefCell::new(0)); let second = Rc::clone(&counter); *second.borrow_mut() += 1; println!("{}", counter.borrow()); }

8. Arc

For shared mutable state across threads, Arc is a standard pattern:

bash
use std::sync::{Arc, Mutex}; let state = Arc::new(Mutex::new(0)); let clone = Arc::clone(&state); std::thread::spawn(move || { let mut value = clone.lock().unwrap(); *value += 1; }).join().unwrap();

Concurrency patterns are covered in depth in Chapter 21.

9. Deref

The Deref trait lets smart pointers behave like references in many contexts. This enables deref coercion, such as passing &String where &str is expected.

10. Drop

Drop runs cleanup when a value leaves scope:

bash
struct Connection; impl Drop for Connection { fn drop(&mut self) { println!("connection released"); } }

You generally do not call drop() as a method. To release early, use std::mem::drop(value).

11. Reference cycles

Two Rc values can form a cycle and prevent their strong counts from reaching zero. Use Weak for non-owning links such as parent pointers.

12. Which type should you choose?

NeedTypical choice
Heap allocation with one ownerBox
Multiple owners, one threadRc
Multiple owners, many threadsArc
Runtime-checked mutation, one threadRefCell
Shared mutable state across threadsArc

13. Common mistakes

  • Using Rc across threads.
  • Using RefCell everywhere to silence borrow-checker design issues.
  • Creating reference cycles.
  • Adding Arc when ownership could stay simple and local.
  • Holding a mutex guard longer than necessary.

Mini lab

Create an in-memory application state containing a request counter. First implement it with Rc for a single-threaded simulation, then convert it to Arc and increment it from multiple threads.

Key takeaways

Smart pointers solve specific ownership problems. Prefer ordinary ownership and references first; reach for Box, Rc, Arc or interior mutability when the data model truly requires them.

Did this help?

Yes No

Join the discussion

0 Comments

Ask questions, add context, or share what worked.

Sign in to join the discussion

No comments yet. Start the discussion.

More from Melochip Editorial

Related articles

Stay in the loop. Get new Melochip articles

Receive Melochip article updates when new articles are available. Explicit opt-in only.

No pre-checked boxes. Unsubscribe support is part of the subscriber foundation.