Labs ICT
โญ Pro Login

Smart Pointers

Box, Rc, and RefCell for advanced ownership.

Smart Pointers in Rust

Smart pointers are data structures that act like pointers but have additional capabilities. Rust has several built-in smart pointers that help manage memory and ownership in different scenarios.

The most common ones are Box<T>, Rc<T>, and RefCell<T>. Each solves a specific problem that regular references can't handle.

fn main() {
    // Box stores data on the heap
    let boxed = Box::new(42);
    println!("Boxed value: {}", boxed);

    // Regular stack-allocated
    let regular = 42;
    println!("Regular value: {}", regular);
}
Try it Yourself ->

Box<T> for Heap Allocation

Box<T> allocates data on the heap instead of the stack. It's useful for large data, recursive types, or when you need owned data.

#[derive(Debug)]
enum List {
    Cons(i32, Box<List>),
    Nil,
}

fn main() {
    let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
    println!("{:?}", list);

    let large_array = Box::new([0; 1000]);
    println!("Array length: {}", large_array.len());
}
Try it Yourself ->

Rc<T> for Reference Counting

Rc<T> (Reference Counted) lets multiple owners share the same data. It's great for when you need shared ownership but not mutation.

use std::rc::Rc;

fn main() {
    let data = Rc::new(vec![1, 2, 3]);
    let data2 = Rc::clone(&data);
    let data3 = Rc::clone(&data);

    println!("Data: {:?}", data);
    println!("Data2: {:?}", data2);
    println!("Data3: {:?}", data3);
    println!("Reference count: {}", Rc::strong_count(&data));
}
Try it Yourself ->

RefCell<T> for Interior Mutability

RefCell<T> allows you to mutate data even when there are immutable references to it. This is called interior mutability and is checked at runtime.

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(vec![1, 2, 3]);

    // Borrow immutably
    println!("Data: {:?}", data.borrow());

    // Borrow mutably
    data.borrow_mut().push(4);
    println!("After push: {:?}", data.borrow());
}
Try it Yourself ->

The Deref Trait

Smart pointers implement the Deref trait, which lets them be used like references. This means you can call methods on them just like regular references.

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let x = MyBox::new(5);
    println!("Value: {}", *x);

    let name = MyBox::new(String::from("Hello"));
    hello(&name);
}

fn hello(name: &str) {
    println!("Hello, {}!", name);
}
Try it Yourself ->

The Drop Trait

The Drop trait lets you run code when a value goes out of scope. This is how smart pointers clean up resources automatically.

struct CustomPointer {
    data: String,
}

impl Drop for CustomPointer {
    fn drop(&mut self) {
        println!("Dropping CustomPointer with data: {}", self.data);
    }
}

fn main() {
    let a = CustomPointer { data: "first".to_string() };
    let b = CustomPointer { data: "second".to_string() };
    println!("CustomPointers created");

    // Drop happens in reverse order
    drop(a);
    println!("a dropped explicitly");
}
Try it Yourself ->

When to Use Each Type

Use Box<T> for heap allocation and recursive types. Use Rc<T> for shared ownership without mutation. Use RefCell<T> for interior mutability. Combine Rc<RefCell<T>> for shared, mutable data.

These smart pointers are essential for building complex data structures and managing memory in Rust programs.

๐Ÿงช Quick Quiz

What smart pointer enables shared ownership?