[Avg. reading time: 15 minutes]

Smart Pointers

Box

What is Box?

Box<T> is a smart pointer that stores data on the heap while keeping a fixed-size pointer on the stack.

  • Stack → pointer (fixed size)
  • Heap → actual data

One reason why using a Box might be beneficial is that it can help you avoid stack overflows. The stack is a fixed-size data structure, so if you try to store a large data structure on the stack, it can overflow and cause your program to crash.

Why Box Exists

Rust must know the size of every type at compile time.

Some types cannot have a known size, such as:

  • Recursive types
  • Trait objects
  • Very large nested structures

Box<T> solves this by introducing indirection:

  • Instead of storing the value directly
  • Store a pointer to the value

Stack - You, Heap - Storage, Box - Label pointing to where the item is stored.

Boxes don’t have performance overhead other than storing their data on the heap instead of on the stack.

This is recursive so will not work without BOX

struct Node {
    value: i32,
    next: Option<Node>,
}

Convert it to Box

struct Node {
    value: i32,
    next: Option<Box<Node>>,
}
use std::mem;

fn main() {
    let x = 10;
    let b = Box::new(x);
    println!("Value: {}", b);
    println!("Size of x: {} bytes", mem::size_of_val(&x));
    println!("Size of Box pointer: {} bytes", mem::size_of_val(&b));
}

b is stored on stack. value 10 is stored on the heap.

fn main() {
    let large_array: [i32; 5] = [1, 2, 3, 4, 5];
    let large_array1: Box<[i32; 5]> = Box::new([1, 2, 3, 4, 5]);

    println!("large_array variable address           : {:p}", &large_array);
    println!("large_array first element address     : {:p}", large_array.as_ptr());

    println!("large_array1 Box variable address     : {:p}", &large_array1);
    println!("large_array1 heap first element addr  : {:p}", large_array1.as_ptr());
}

large_array - both variable and value are in stack. large_array1 - variable is in stack, and value is in heap

Better Example

use std::mem;

#[derive(Debug)]
struct Class {
    id: i32,
    fname: String,
    lname: String,
}

fn main() {
    let c = Class {
        id: 34,
        fname: String::from("Rachel"),
        lname: String::from("Green"),
    };

    println!("\nBefore boxing\n");

    println!("Address of c (local variable)              : {:p}", &c);
    println!("Size of Class value                        : {} bytes", mem::size_of_val(&c));

    println!("Address of c.id                            : {:p}", &c.id);

    println!("Address of c.fname String object           : {:p}", &c.fname);
    println!("Heap buffer of c.fname                     : {:p}", c.fname.as_ptr());

    println!("Address of c.lname String object           : {:p}", &c.lname);
    println!("Heap buffer of c.lname                     : {:p}", c.lname.as_ptr());

    println!("Value of c                                 : {:?}", c);

    let boxed_class = Box::new(c);

    println!("\nAfter boxing\n");

    println!("Address of boxed_class variable            : {:p}", &boxed_class);
    println!("Size of Box pointer                        : {} bytes", mem::size_of_val(&boxed_class));

    println!("Address of boxed Class value               : {:p}", &*boxed_class);
    println!("Size of boxed Class value                  : {} bytes", mem::size_of_val(&*boxed_class));

    println!("Address of boxed_class.id                  : {:p}", &boxed_class.id);

    println!("Address of boxed_class.fname String object : {:p}", &boxed_class.fname);
    println!("Heap buffer of boxed_class.fname           : {:p}", boxed_class.fname.as_ptr());

    println!("Address of boxed_class.lname String object : {:p}", &boxed_class.lname);
    println!("Heap buffer of boxed_class.lname           : {:p}", boxed_class.lname.as_ptr());

    println!("Value of boxed_class                       : {:?}", boxed_class);

    let unboxed_class: Class = *boxed_class;

    println!("\nAfter unboxing\n");

    println!("Address of unboxed_class                   : {:p}", &unboxed_class);
    println!("Size of unboxed Class value                : {} bytes", mem::size_of_val(&unboxed_class));

    println!("Address of unboxed_class.fname String obj  : {:p}", &unboxed_class.fname);
    println!("Heap buffer of unboxed_class.fname         : {:p}", unboxed_class.fname.as_ptr());

    println!("Address of unboxed_class.lname String obj  : {:p}", &unboxed_class.lname);
    println!("Heap buffer of unboxed_class.lname         : {:p}", unboxed_class.lname.as_ptr());

    println!("Value of unboxed_class                     : {:?}", unboxed_class);
}

mem::size_of_val(&value) gives the size of the value itself.

It does not include extra heap memory owned by fields like String.

In this example:

  • Class contains id, fname, and lname
  • fname and lname are String values
  • each String stores its text in a separate heap buffer

So boxing Class moves the outer struct to the heap, but the string contents were already heap allocated.

Linked List Example

#![allow(unused)]
fn main() {
// Linked List

Here is an example of a linked list that could be stored on the stack in Rust:

struct Node {
    value: i32,
    next: Option<Box<Node>>,
}

struct LinkedList {
    head: Option<Box<Node>>,
}
}

In this example, the Node struct represents a single node in the linked list, and the LinkedList struct represents the entire linked list. The head field of the LinkedList struct contains a reference to the first node in the list, and each Node struct has a next field that contains a reference to the next node in the list.

Because this data structure is relatively small, it can be stored on the stack without problems. However, if the linked list became very large, it could cause a stack overflow, in which case it would be better to store it on the heap using a Box type.


Visual DSA Tool (https://csvistool.com/)

#smartpointers #box #linkedlistVer 2.0.22

Last change: 2026-04-01