Skip to content

Smart Pointers & Memory Management Deep Tech

Quản lý Memory phức tạp một cách an toàn

Box<T> - Heap Allocation

Cơ bản

Box<T> allocate T trên heap thay vì stack:

rust
fn main() {
    // Stack allocation
    let x: i32 = 5;  // 4 bytes on stack
    
    // Heap allocation với Box
    let boxed: Box<i32> = Box::new(5);  // pointer on stack, i32 on heap
    
    // Dereference để access value
    println!("Value: {}", *boxed);
}

Memory Layout

STACK                              HEAP
┌─────────────────┐               ┌─────────────────┐
│ boxed: Box<i32> │───────────────▶│       5         │
│   (8 bytes ptr) │               │   (4 bytes i32) │
└─────────────────┘               └─────────────────┘

Use Cases

rust
// 1. Recursive types (size unknown at compile time)
enum List {
    Cons(i32, Box<List>),
    Nil,
}

let list = List::Cons(1,
    Box::new(List::Cons(2,
        Box::new(List::Cons(3,
            Box::new(List::Nil))))));

// 2. Large data - avoid stack overflow
struct LargeData {
    buffer: [u8; 1_000_000],  // 1MB
}

// ❌ Stack overflow risk
// let data = LargeData { buffer: [0; 1_000_000] };

// ✅ Heap allocation
let data = Box::new(LargeData { buffer: [0; 1_000_000] });

// 3. Trait objects (dyn Trait)
let handler: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);

Box Internals

rust
// Box is essentially:
#[repr(transparent)]
pub struct Box<T: ?Sized>(Unique<T>);

// When dropped, Box:
// 1. Calls T::drop() if T implements Drop
// 2. Deallocates heap memory

Rc<T> - Reference Counting

Shared Ownership (Single-threaded)

Rc<T> cho phép nhiều owners cho cùng một data:

rust
use std::rc::Rc;

fn main() {
    let a = Rc::new(String::from("hello"));
    println!("Count after a: {}", Rc::strong_count(&a)); // 1
    
    let b = Rc::clone(&a);  // Increment count, NOT deep clone
    println!("Count after b: {}", Rc::strong_count(&a)); // 2
    
    {
        let c = Rc::clone(&a);
        println!("Count in scope: {}", Rc::strong_count(&a)); // 3
    }
    
    println!("Count after scope: {}", Rc::strong_count(&a)); // 2
    // Data freed when count reaches 0
}

Memory Layout

      STACK                           HEAP
    ┌───────┐                 ┌─────────────────────────┐
  a │ Rc ───┼─────────────────▶│ strong_count: 2        │
    └───────┘                 ├─────────────────────────┤
                              │ weak_count: 1          │
    ┌───────┐                 ├─────────────────────────┤
  b │ Rc ───┼─────────────────▶│ data: "hello"          │
    └───────┘                 └─────────────────────────┘
              
    (Cả a và b trỏ đến CÙNG allocation)

Reference Counting Code

rust
// Simplified Rc internals
struct RcBox<T> {
    strong: Cell<usize>,
    weak: Cell<usize>,
    value: T,
}

impl<T> Clone for Rc<T> {
    fn clone(&self) -> Rc<T> {
        // Increment count (NOT atomic, single-thread only)
        self.inner().strong.set(self.inner().strong.get() + 1);
        Rc { ptr: self.ptr }
    }
}

impl<T> Drop for Rc<T> {
    fn drop(&mut self) {
        // Decrement count
        let count = self.inner().strong.get() - 1;
        self.inner().strong.set(count);
        
        if count == 0 {
            // Drop the value
            unsafe { drop_in_place(&mut self.inner().value); }
            
            // Deallocate if no weak refs
            if self.inner().weak.get() == 0 {
                dealloc(self.ptr);
            }
        }
    }
}

⚠️ KHÔNG THREAD-SAFE

Rc<T> sử dụng non-atomic operations. KHÔNG được share qua threads:

rust
use std::rc::Rc;
use std::thread;

let data = Rc::new(42);
// thread::spawn(move || {
//     println!("{}", data);  // ERROR: Rc<i32> is not Send
// });

Arc<T> - Atomic Reference Counting

Thread-safe Shared Ownership

Arc<T> = Rc<T> + atomic operations:

rust
use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    let mut handles = vec![];
    
    for i in 0..3 {
        let data = Arc::clone(&data);  // Atomic increment
        handles.push(thread::spawn(move || {
            println!("Thread {}: {:?}", i, data);
        }));
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
}

Atomic Operations

rust
// Arc uses atomic operations (simplified)
impl<T> Clone for Arc<T> {
    fn clone(&self) -> Arc<T> {
        // Atomic increment - thread-safe
        self.inner().strong.fetch_add(1, Ordering::Relaxed);
        Arc { ptr: self.ptr }
    }
}

impl<T> Drop for Arc<T> {
    fn drop(&mut self) {
        // Atomic decrement with proper ordering
        if self.inner().strong.fetch_sub(1, Ordering::Release) != 1 {
            return;
        }
        
        // Synchronize before accessing data
        atomic::fence(Ordering::Acquire);
        
        // Now safe to drop
        unsafe { drop_in_place(&mut self.inner().data); }
    }
}

Performance: Rc vs Arc

rust
// Benchmark: 10M clone + drop operations
// Rc:  ~150ms (non-atomic increment/decrement)
// Arc: ~450ms (atomic operations, cache synchronization)

// Rule: Use Rc for single-threaded, Arc only when needed

RefCell<T> - Interior Mutability

Runtime Borrow Checking

RefCell<T> cho phép mutable borrow của data bên trong &T:

rust
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);
    
    // Immutable borrow
    {
        let borrowed = data.borrow();
        println!("Value: {}", *borrowed);
    }
    
    // Mutable borrow
    {
        let mut borrowed = data.borrow_mut();
        *borrowed += 10;
    }
    
    println!("Final: {:?}", data);  // RefCell { value: 15 }
}

Borrow Rules at Runtime

rust
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(vec![1, 2, 3]);
    
    let borrow1 = data.borrow();      // OK: first immutable borrow
    let borrow2 = data.borrow();      // OK: multiple immutable borrows
    
    // ❌ PANIC at runtime!
    // let mut_borrow = data.borrow_mut();  // Can't mut borrow while immutable exists
    
    drop(borrow1);
    drop(borrow2);
    
    let mut_borrow = data.borrow_mut();  // OK now
}

RefCell Internals

rust
// Simplified RefCell
pub struct RefCell<T> {
    borrow: Cell<isize>,  // >0: shared borrows, -1: exclusive borrow
    value: UnsafeCell<T>,
}

impl<T> RefCell<T> {
    pub fn borrow(&self) -> Ref<T> {
        match self.borrow.get() {
            b if b >= 0 => {
                self.borrow.set(b + 1);  // Increment shared count
                Ref { ... }
            }
            _ => panic!("already mutably borrowed"),
        }
    }
    
    pub fn borrow_mut(&self) -> RefMut<T> {
        match self.borrow.get() {
            0 => {
                self.borrow.set(-1);  // Mark as exclusively borrowed
                RefMut { ... }
            }
            _ => panic!("already borrowed"),
        }
    }
}

Common Pattern: Rc + RefCell

rust
use std::cell::RefCell;
use std::rc::Rc;

// Shared mutable state (single-threaded)
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let parent = Rc::new(Node {
        value: 1,
        children: RefCell::new(vec![]),
    });
    
    let child = Rc::new(Node {
        value: 2,
        children: RefCell::new(vec![]),
    });
    
    // Mutate through shared reference
    parent.children.borrow_mut().push(Rc::clone(&child));
}

Weak<T> - Breaking Cycles

Reference Cycle Problem

rust
use std::cell::RefCell;
use std::rc::Rc;

struct Node {
    value: i32,
    parent: RefCell<Option<Rc<Node>>>,  // ❌ Creates cycle!
    children: RefCell<Vec<Rc<Node>>>,
}

// parent → child → parent → ... (memory leak!)

Solution: Weak References

rust
use std::cell::RefCell;
use std::rc::{Rc, Weak};

struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,       // ✅ Weak doesn't count
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let parent = Rc::new(Node {
        value: 1,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });
    
    let child = Rc::new(Node {
        value: 2,
        parent: RefCell::new(Rc::downgrade(&parent)),  // Weak ref to parent
        children: RefCell::new(vec![]),
    });
    
    parent.children.borrow_mut().push(Rc::clone(&child));
    
    // Access parent from child
    if let Some(parent_rc) = child.parent.borrow().upgrade() {
        println!("Parent value: {}", parent_rc.value);
    }
}

Weak Behavior

rust
use std::rc::{Rc, Weak};

fn main() {
    let weak: Weak<i32>;
    
    {
        let strong = Rc::new(42);
        weak = Rc::downgrade(&strong);
        
        // upgrade() returns Some while strong exists
        assert!(weak.upgrade().is_some());
    }
    // strong dropped, data deallocated
    
    // upgrade() returns None after data gone
    assert!(weak.upgrade().is_none());
}

Interior Mutability Patterns

Cell vs RefCell

rust
use std::cell::{Cell, RefCell};

// Cell<T>: Copy in, Copy out (T must be Copy)
let cell = Cell::new(5);
cell.set(10);
let value = cell.get();  // Returns copy

// RefCell<T>: Borrow checking at runtime
let refcell = RefCell::new(vec![1, 2, 3]);
refcell.borrow_mut().push(4);  // Borrow and modify

OnceCell & LazyCell

rust
use std::cell::OnceCell;

// Initialize once, use many times
static CONFIG: OnceCell<Config> = OnceCell::new();

fn get_config() -> &'static Config {
    CONFIG.get_or_init(|| {
        Config::load_from_file()
    })
}

Comparison Table

TypeOwnershipThread-safeMutabilityUse Case
Box<T>SingleSame as TVia &mutHeap allocation
Rc<T>SharedImmutableSingle-thread sharing
Arc<T>SharedImmutableMulti-thread sharing
Cell<T>SingleCopy in/outSimple interior mut
RefCell<T>SingleRuntime borrowComplex interior mut
Mutex<T>SharedLock-basedThread-safe mutation

Bảng Tóm tắt

rust
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::cell::{Cell, RefCell};

// === HEAP ALLOCATION ===
let boxed = Box::new(42);                    // Single owner, heap

// === SHARED OWNERSHIP ===
let rc = Rc::new(42);                        // Single-thread shared
let rc2 = Rc::clone(&rc);                    // Increment count

let arc = Arc::new(42);                      // Thread-safe shared
let arc2 = Arc::clone(&arc);                 // Atomic increment

// === WEAK REFERENCES ===
let weak = Rc::downgrade(&rc);               // Doesn't prevent dealloc
let strong = weak.upgrade();                 // Returns Option<Rc<T>>

// === INTERIOR MUTABILITY ===
let cell = Cell::new(42);
cell.set(100);                               // Copy-based

let refcell = RefCell::new(vec![]);
refcell.borrow_mut().push(1);                // Runtime borrow check

// === COMMON PATTERNS ===
Rc<RefCell<T>>                               // Single-thread shared mutation
Arc<Mutex<T>>                                // Multi-thread shared mutation