# Rust Smart Pointers Explained: Box, Rc, Arc and RefCell in 2026 > Rust smart pointers Box, Rc, Arc and RefCell explained with compilable 2026 examples, a decision table and common interview questions. - Published: 2026-07-05 - Updated: 2026-07-06 - Author: SharpSkill - Tags: rust, smart-pointers, memory-management, interview, rust-2024-edition - Reading time: 11 min --- Rust smart pointers are the tools that unlock ownership patterns the borrow checker alone cannot express: heap allocation, shared ownership, and mutation behind a shared reference. Where a plain reference (`&T`) only borrows a value, a smart pointer owns its data and layers extra behavior on top of it. This guide breaks down the four types every Rust developer meets in production and in interviews: `Box`, `Rc`, `Arc` and `RefCell`, with compilable examples targeting the Rust 2024 edition. > **The one-line summary** > > Use `Box` for single-owner heap allocation, `Rc` for shared ownership on one thread, `Arc` for shared ownership across threads, and `RefCell` to mutate a value through a shared reference. The pairs `Rc>` and `Arc>` cover shared mutable state. ## What smart pointers are in Rust A smart pointer is a struct that behaves like a pointer but carries extra metadata or capabilities. Most implement the `Deref` trait, so `*pointer` and method calls work as if the pointer were a plain reference, and the `Drop` trait, so cleanup runs automatically when the value leaves scope. The standard library ships the four covered here, and understanding them depends on a solid grasp of [ownership and borrowing](/blog/rust/ownership-borrowing-rust-complete-guide), which decides who frees each allocation and when. The key distinction from a reference is ownership. `&T` never owns the data it points at, so it cannot outlive the value. A smart pointer owns the data, controls its lifetime, and frees it deterministically. The [Rust Book chapter on smart pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html) treats them as a category precisely because they share this ownership-plus-behavior shape. ## `Box`: heap allocation for recursive and sized types `Box` is the simplest smart pointer. It stores a value on the heap and holds a pointer to it on the stack, with a single owner and no runtime overhead beyond the allocation itself. Its most common job is giving recursive types a known size: a type that contains itself directly would be infinitely large, but a `Box` is just a pointer, so its size is fixed regardless of what it points to. The example below defines a binary tree. Each `Node` holds two children, and without indirection the compiler cannot compute the size of `Tree`. Wrapping each child in a `Box` breaks the recursion at the type level. ```rust // tree.rs #[derive(Debug)] enum Tree { Leaf(i32), // Box puts each child on the heap, so Node has a fixed size (two pointers). Node(Box, Box), } fn sum(tree: &Tree) -> i32 { match tree { Tree::Leaf(value) => *value, // base case: return the leaf Tree::Node(left, right) => sum(left) + sum(right), // recurse into both children } } fn main() { // Build (1) + ((2) + (3)) = 6 let tree = Tree::Node( Box::new(Tree::Leaf(1)), Box::new(Tree::Node( Box::new(Tree::Leaf(2)), Box::new(Tree::Leaf(3)), )), ); println!("sum = {}", sum(&tree)); // sum = 6 } ``` `Box` also matters when moving a large value would be expensive, or when returning a trait object such as `Box`. In every case the rule is the same: one owner, freed automatically when the `Box` drops. ## `Rc`: shared ownership in single-threaded code Sometimes a value needs several owners, none of which is obviously the last to use it. `Rc` (reference counted) solves this by keeping a strong count of how many owners exist. `Rc::clone` increments that count without copying the underlying data, and each drop decrements it. When the count hits zero, the value is freed. Consider a configuration object that several workers read. Each worker should hold the config for as long as it needs it, and the allocation should disappear only when the last worker is gone. ```rust // shared_config.rs use std::rc::Rc; #[derive(Debug)] struct Config { endpoint: String, timeout_ms: u32, } fn main() { // Rc::new moves Config onto the heap with a strong count of 1. let config = Rc::new(Config { endpoint: "https://api.example.com".to_string(), timeout_ms: 5000, }); // Rc::clone only bumps the reference count; it does not deep-copy Config. let worker_a = Rc::clone(&config); let worker_b = Rc::clone(&config); // All three handles point to the same allocation. println!("endpoint: {}", worker_a.endpoint); println!("timeout: {}", worker_b.timeout_ms); // strong_count reports how many owners currently hold the value. println!("owners: {}", Rc::strong_count(&config)); // owners: 3 } ``` Calling `Rc::clone` explicitly (rather than `config.clone()`) is idiomatic: it signals to readers that the operation is cheap and only touches a counter. The catch is that `Rc` hands out shared, immutable references only. It cannot mutate the value it shares, and it is not safe to send across threads. The [`Rc` documentation](https://doc.rust-lang.org/std/rc/struct.Rc.html) spells out both constraints. ## `RefCell` and interior mutability in Rust Rust normally enforces that a value is either shared through many immutable references or mutated through exactly one mutable reference, and it checks this at compile time. `RefCell` provides interior mutability: it lets code mutate a value through a shared reference by moving that same check to runtime. `borrow()` returns a shared read guard; `borrow_mut()` returns an exclusive write guard. The rules are identical, but violations panic instead of failing to compile. Combined with `Rc`, this produces the canonical single-threaded shared-mutable pattern, `Rc>`: several owners that can all update the same value. ```rust // counter.rs use std::rc::Rc; use std::cell::RefCell; // Rc gives shared ownership; RefCell allows mutation through a shared reference. type SharedCounter = Rc>; fn increment(counter: &SharedCounter) { // borrow_mut() hands out an exclusive reference, checked at runtime. *counter.borrow_mut() += 1; } fn main() { let counter: SharedCounter = Rc::new(RefCell::new(0)); let handle_a = Rc::clone(&counter); let handle_b = Rc::clone(&counter); increment(&handle_a); increment(&handle_b); increment(&counter); // borrow() gives a shared read guard; the value is now 3. println!("count = {}", counter.borrow()); // count = 3 } ``` > **RefCell moves borrow checking to runtime** > > Calling `borrow_mut()` while another `borrow()` or `borrow_mut()` guard is still alive panics with `already borrowed: BorrowMutError`. The safety guarantees hold, but a logic bug that the compiler would have caught becomes a crash at runtime. Keep guards short-lived and avoid holding a borrow across a function call that might re-enter the same `RefCell`. Interior mutability is also where reference cycles hide. Two `Rc>` values that point at each other keep each other's strong count above zero forever, so neither is ever freed. The fix is `Weak`, a non-owning handle that does not affect the strong count; the classic walkthrough lives in [Learn Rust With Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/). ## `Arc`: thread-safe reference counting for concurrency `Arc` (atomically reference counted) is the multi-threaded sibling of `Rc`. The public API is nearly identical, but the counter uses atomic operations, so cloning and dropping an `Arc` from several threads at once stays correct. That atomicity is why `Arc` is chosen only when sharing crosses a thread boundary: the atomic increment is measurably slower than the plain one `Rc` uses. Because `Arc` still hands out immutable references, mutation across threads needs a synchronization primitive. `Mutex` is the usual partner, giving the `Arc>` pattern that mirrors `Rc>` for concurrent code. The example spawns four threads that each increment a shared total a thousand times. ```rust // parallel_sum.rs use std::sync::{Arc, Mutex}; use std::thread; fn main() { // Arc is safe to share across threads; Mutex serializes access to the inner value. let total = Arc::new(Mutex::new(0u64)); let mut handles = Vec::new(); for _ in 0..4 { // Each thread gets its own Arc handle (an atomic count bump). let total = Arc::clone(&total); handles.push(thread::spawn(move || { for _ in 0..1000 { // lock() blocks until the mutex is free, then returns a guard. let mut value = total.lock().unwrap(); *value += 1; } })); } for handle in handles { handle.join().unwrap(); // wait for every thread before reading } println!("total = {}", *total.lock().unwrap()); // total = 4000 } ``` The compiler enforces the boundary for you: `Rc` is not `Send`, so trying to move one into `thread::spawn` fails to compile, pushing you toward `Arc`. Reaching for `Arc` inside a single thread just pays for atomics you never use. The [`Arc` documentation](https://doc.rust-lang.org/std/sync/struct.Arc.html) covers the memory-ordering guarantees in detail, and `Arc>` underpins much of the shared state in [async Rust with Tokio](/blog/rust/rust-async-await-tokio-futures-concurrency). ## Box vs Rc vs Arc vs RefCell: when to use each The four types compose along two axes: how many owners a value has, and whether it can be mutated through a shared handle. The table summarizes the trade-offs. | Type | Ownership | Mutation via shared handle | Thread-safe | Overhead | |------|-----------|----------------------------|-------------|----------| | `Box` | Single | No | Yes, if `T: Send` | Heap allocation only | | `Rc` | Shared | No | No | Non-atomic count | | `Arc` | Shared | No | Yes | Atomic count | | `RefCell` | Single | Yes (runtime-checked) | No | Runtime borrow flag | The practical decision tree is short. Need one owner on the heap: `Box`. Need many owners on one thread: `Rc`. Need many owners across threads: `Arc`. Need to mutate a shared value: wrap the inner type in `RefCell` (single-thread) or `Mutex` (multi-thread). The Effective Rust guidance on [reference and pointer types](https://www.lurklurk.org/effective-rust/) reaches the same layering. Start with the least powerful option and add capability only when the compiler forces it. ## Rust smart pointers interview questions Smart pointers are a reliable interview topic because they test whether a candidate understands ownership rather than just syntax. The questions below appear often, and more are collected in the [Rust smart pointers interview module](/technologies/rust/interview-questions/smart-pointers). **What is the difference between `Rc` and `Arc`?** Both provide shared ownership through reference counting. `Rc` uses a non-atomic counter and is confined to a single thread; `Arc` uses atomic operations and can be shared across threads at a small runtime cost. The compiler enforces the split: `Rc` is neither `Send` nor `Sync`, so it cannot cross a thread boundary. **Why combine `Rc` with `RefCell`?** `Rc` grants shared ownership but only immutable access. `RefCell` adds interior mutability, letting owners mutate the value through a shared reference with runtime borrow checks. Together, `Rc>` is the idiomatic single-threaded shared-mutable building block. **How can reference counting leak memory in Rust?** Two `Rc` (or `Arc`) values that reference each other form a cycle whose strong counts never reach zero, so the allocation is never freed. Breaking the cycle with `Weak`, which holds a non-owning reference, restores correct cleanup. **When is `Box` preferable to `Rc`?** Whenever a single owner suffices. `Box` has no reference-counting overhead, so it is the default for heap allocation, recursive types, and trait objects. Reach for `Rc` only when genuine shared ownership is required. ## Conclusion Rust smart pointers turn ownership from a constraint into a set of composable building blocks. The choice among them follows directly from the shape of the problem: - Reach for `Box` when a value needs a single heap owner, a recursive type needs a fixed size, or a function returns a trait object. - Use `Rc` for multiple owners on one thread, and switch to `Arc` the moment ownership crosses a thread boundary. - Add `RefCell` for interior mutability in single-threaded code, and `Mutex` for the concurrent equivalent, keeping borrow and lock guards short-lived. - Combine them deliberately: `Rc>` for shared mutable state on one thread, `Arc>` across threads. - Watch for reference cycles between counted pointers and break them with `Weak` to avoid leaks. - Default to the least powerful type and let the compiler tell you when more capability is required. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/rust/rust-smart-pointers-box-rc-arc-refcell