Memory Management in Rust
Understand Box<T>, Rc<T>, RefCell<T>, Arc<T>, memory layout, and the basics of unsafe Rust.
Box<T> — Heap Allocation
Box<T> allocates a value on the heap and gives you a single-owner pointer to it.
fn main() {
let b = Box::new(5);
println!("b = {}", b); // dereferences automatically
// Explicit dereference
println!("*b = {}", *b);
// Useful for large values (avoids stack overflow)
let big_array = Box::new([0u8; 1_000_000]);
println!("allocated {} bytes on heap", big_array.len());
} // big_array is freed here
Recursive Data Structures
Without Box, the size of a recursive type would be infinite:
// ERROR: recursive type has infinite size
// enum List { Cons(i32, List), Nil }
// FIX: Box breaks the recursion — pointer has known size
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
impl List {
fn new() -> Self { List::Nil }
fn prepend(self, val: i32) -> Self {
List::Cons(val, Box::new(self))
}
fn sum(&self) -> i32 {
match self {
List::Cons(val, next) => val + next.sum(),
List::Nil => 0,
}
}
}
fn main() {
let list = List::new().prepend(3).prepend(2).prepend(1);
println!("sum: {}", list.sum()); // 6
}
Rc<T> — Reference Counting (Single-Threaded)
Rc<T> allows multiple owners of the same heap value. The value is dropped when the reference count reaches zero.
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a); // increment reference count
let c = Rc::clone(&a);
println!("count: {}", Rc::strong_count(&a)); // 3
println!("{} {} {}", a, b, c);
drop(b);
println!("count after drop: {}", Rc::strong_count(&a)); // 2
} // a and c dropped here, count -> 0, string freed
Rc<T> is not thread-safe (!Send). Use Arc<T> for multi-threaded scenarios.
RefCell<T> — Interior Mutability
RefCell<T> allows mutation through a shared reference by deferring borrow checks to runtime:
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
// Multiple shared borrows
{
let r1 = data.borrow();
let r2 = data.borrow();
println!("{:?} {:?}", *r1, *r2);
} // r1, r2 released
// Mutable borrow
data.borrow_mut().push(4);
println!("{:?}", data.borrow());
}
Rc<RefCell<T>> — Shared Mutable Data
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
children: Vec<Rc<RefCell<Node>>>,
}
impl Node {
fn new(value: i32) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Node { value, children: Vec::new() }))
}
fn add_child(parent: &Rc<RefCell<Node>>, child: Rc<RefCell<Node>>) {
parent.borrow_mut().children.push(child);
}
}
fn main() {
let root = Node::new(1);
let child1 = Node::new(2);
let child2 = Node::new(3);
Node::add_child(&root, Rc::clone(&child1));
Node::add_child(&root, child2);
println!("root children: {}", root.borrow().children.len()); // 2
}
Arc<T> — Atomic Reference Counting (Multi-Threaded)
Arc<T> is Rc<T> with atomic operations — safe to share across threads:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let shared = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let s = Arc::clone(&shared);
handles.push(thread::spawn(move || {
*s.lock().unwrap() += 1;
}));
}
for h in handles { h.join().unwrap(); }
println!("{}", *shared.lock().unwrap()); // 5
}
Cell<T> — Copy-Type Interior Mutability
Cell<T> is a lightweight alternative to RefCell<T> for Copy types:
use std::cell::Cell;
struct Config {
debug: Cell<bool>,
level: Cell<u32>,
}
impl Config {
fn new() -> Self {
Config { debug: Cell::new(false), level: Cell::new(1) }
}
fn enable_debug(&self) { self.debug.set(true); }
fn set_level(&self, l: u32) { self.level.set(l); }
}
fn main() {
let cfg = Config::new();
cfg.enable_debug(); // mutate through &self!
cfg.set_level(3);
println!("debug={}, level={}", cfg.debug.get(), cfg.level.get());
}
Memory Layout
Understanding how Rust lays out data in memory:
fn main() {
use std::mem::{size_of, align_of};
println!("bool: size={} align={}", size_of::<bool>(), align_of::<bool>());
println!("u8: size={} align={}", size_of::<u8>(), align_of::<u8>());
println!("i32: size={} align={}", size_of::<i32>(), align_of::<i32>());
println!("f64: size={} align={}", size_of::<f64>(), align_of::<f64>());
println!("&str: size={}", size_of::<&str>()); // 16 (ptr + len)
println!("String: size={}", size_of::<String>()); // 24 (ptr + len + cap)
println!("Vec<u8>: size={}", size_of::<Vec<u8>>()); // 24
println!("Box<i32>: size={}", size_of::<Box<i32>>()); // 8 (pointer)
println!("Option<&i32>: size={}", size_of::<Option<&i32>>()); // 8 (null opt.)
println!("Option<i32>: size={}", size_of::<Option<i32>>()); // 8
}
Unsafe Rust
unsafe unlocks five additional capabilities. Use sparingly and document invariants:
fn main() {
// 1. Dereference raw pointers
let x = 42;
let r = &x as *const i32; // create raw pointer (safe)
unsafe {
println!("{}", *r); // dereference (unsafe)
}
// 2. Mutable raw pointer
let mut y = 10;
let p = &mut y as *mut i32;
unsafe {
*p += 1;
}
println!("{}", y); // 11
}
// 3. Call unsafe functions
unsafe fn dangerous(ptr: *const u8, len: usize) -> &'static str {
let slice = std::slice::from_raw_parts(ptr, len);
std::str::from_utf8(slice).unwrap()
}
// Safe wrapper
fn safe_from_bytes(bytes: &[u8]) -> &str {
unsafe { dangerous(bytes.as_ptr(), bytes.len()) }
}
fn main() {
let bytes = b"hello";
println!("{}", safe_from_bytes(bytes));
}
// 4. Implementing unsafe traits
unsafe trait Zeroable {}
unsafe impl Zeroable for u8 {}
unsafe impl Zeroable for i32 {}
fn zero_out<T: Zeroable>(val: &mut T) {
unsafe {
let size = std::mem::size_of::<T>();
let ptr = val as *mut T as *mut u8;
std::ptr::write_bytes(ptr, 0, size);
}
}
Smart Pointer Summary
| Type | Ownership | Thread safe | Interior mut | Use case |
|---|---|---|---|---|
Box<T> | Unique | Yes (T: Send) | No | Heap alloc, recursive types, dyn Trait |
Rc<T> | Shared | No | No | Single-threaded shared ownership |
Arc<T> | Shared | Yes | No | Multi-threaded shared ownership |
Cell<T> | Unique | No | Yes (Copy) | Cheap interior mutation |
RefCell<T> | Unique | No | Yes (any) | Runtime borrow checking |
Mutex<T> | Shared | Yes | Yes | Thread-safe mutation |
RwLock<T> | Shared | Yes | Yes | Many readers OR one writer |