Box

Store Data on the Heap with Box

book-rust-box Rust by example - box std

All values in Rust are stack-allocated by default. Box<T> allow you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data. Box<T> owns its inner data and drop its contents when it goes out of scope:

//! Basic `Box` usage.

/// An example struct, which does not implement the `Copy` trait.
/// This means that when an instance of `MyStruct` is moved, the original
/// instance is no longer valid.
#[derive(Debug)]
struct MyStruct(i32);

fn main() {
    // `in_stack` is an instance of `MyStruct` allocated on the stack.
    let in_stack = MyStruct(5);

    // Create a `Box`. This moves `in_stack` to the heap,
    // and `in_heap` becomes a pointer to this heap-allocated data.
    let in_heap: Box<MyStruct> = Box::new(in_stack);
    // `in_stack` is no longer valid because it has been moved into the `Box`.
    // ERROR: println!("{:?}", in_stack);

    // Dereference the `Box` to get the value,
    // moving the value from the heap to the stack:
    let in_stack = *in_heap;
    println!("{:?}", in_stack);
    // ERROR: println!("{:?}", in_heap); // `in_heap` has been moved.

    // If the inner value is `Copy`:
    let x: u32 = 7;
    // `x` is copied, `y` owns a new `i32` on the heap.
    let y: Box<u32> = Box::new(x);
    println!("x: {}, *y: {}", x, *y);

    // `Box` owns its value.
    // When `my_boxed_string` goes out of scope (is dropped),
    // the String on the heap is deallocated.
    {
        let my_boxed_string = Box::new(String::from("Hello, Box!"));
        println!("my_boxed_string: {}", my_boxed_string);
    } // `my_boxed_string` goes out of scope here, and the memory is freed.
    println!("my_boxed_string is out of scope.");
    // ERROR: println!("my_boxed_string: {}", my_boxed_string);
}

The Box<T> type is a smart pointer, because it implements the std::ops::Deref⮳ trait, which allows Box<T> values to be treated just like a reference. You can use the de-reference operator * or 'deref coercion' with the . operator to use its inner value:

fn main() {
    // Dereferencing with `*`:
    let boxed: Box<u8> = Box::new(1);
    // Retrieve its inner value.
    let val: u8 = *boxed;
    println!("{}", val);

    // Dereferencing with `.`: `Box<T>` behaves as if it were `T`.
    let boxed = Box::new("example");
    // Equivalent to (*boxed.deref()).len():
    let val = boxed.len();
    println!("{}", val);
}

Box Use Cases

std

Use Box<T> when

  • you don't want to rely on stack space;
  • you have a large amount of data and you want to avoid copying it on the stack: If you have a very large struct, you might want to allocate it on the heap using Box to avoid stack overflow.
  • you have a dynamically sized type, whose size can't be known at compile time. This is common with recursive data structures like linked lists, trees, or enums where one variant contains another instance of the same enum.
  • you want to own a value and you care only that it's a type that implements a particular trait rather than being of a specific type.
  • When you need to transfer ownership of data between functions where you need a stable address - although Arc<T> or Rc<T> might be more appropriate for shared ownership.

Do not use Box for small, fixed-size types, or when you only need a reference: If you just need to borrow a value, use &T (immutable reference) or &mut T (mutable reference).

Use Box to store Dynamically Sized Types

std

Dynamically Sized Types (DSTs), also known as "unsized types," are types whose size cannot be determined at compile time. The most common DSTs are:

  • Slices: [T] (e.g., [i32], [u8]),
  • str: The string slice type,
  • Trait Objects: dyn Trait (e.g., dyn std::io::Read) - see the example below.
fn main() {
    // Creating a Box containing a slice.
    let boxed_slice: Box<[i32]> = Box::new([1, 2, 3]);

    println!("Boxed slice: {:?}", boxed_slice);
    println!("Length of boxed slice: {}", boxed_slice.len()); // via `Deref`.

    // You can also convert a `Vec` into a `Box<[T]>`, consuming the `Vec` and
    // avoiding a copy. `Box<[T]>` may be more compact than `Vec` but is not
    // resizable.
    let my_vec = vec![4, 5, 6, 7];
    let boxed_from_vec: Box<[i32]> = my_vec.into_boxed_slice();
    println!("Boxed from Vec: {:?}", boxed_from_vec);

    // Creating a Box containing a str
    let boxed_str: Box<str> = "Hello, Rust!".to_string().into_boxed_str();
    println!("Boxed string: {}", boxed_str);

    // You can also create it directly from a &str literal (though less common)
    let another_boxed_str: Box<str> = Box::from("Another string slice");
    println!("Another boxed string: {}", another_boxed_str);
}

Use Box as a Smart Pointer to Owned Trait Objects

std

Trait objects dyn SomeTrait are opaque values that implements a base trait SomeTrait. The purpose of trait objects is to permit "late binding", a.k.a. virtual dispatch. When you call a function through a dyn SomeTrait, the compiler inserts code to lookup the function to call at runtime, allowing for polymorphism.

Because their underlying concrete types are obscured, trait objects are dynamically sized types (DSTs). Like all DSTs, trait objects must be used behind some type of pointer, for example &dyn SomeTrait or Box<dyn SomeTrait>. If you want an owned trait object use Box<dyn Trait>, and if you want a borrowed trait object use &dyn Trait.

//! Example of using `Box` as a smart pointer for trait objects.
//!
//! `Box` can be used to store an owned trait object on the heap.

// Let's define an example trait and implement it on two structs.
trait Shape {
    fn area(&self) -> f64;
}

struct Circle {
    radius: f64,
}

impl Shape for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

struct Rectangle {
    width: f64,
    height: f64,
}

impl Shape for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

fn main() {
    // Put different concrete types that implement `Shape` into a `Vec<Box<dyn
    // Shape>>`.
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 5.0 }),
        Box::new(Rectangle {
            width: 3.0,
            height: 4.0,
        }),
    ];

    // Dynamic dispatch: the `area` methods of the underlying concrete types
    // (`Circle` then `Rectangle`) are called.
    for shape in shapes {
        println!("Shape area: {:.2}", shape.area());
    }
}

Implement Recursive Data Structures with Box

std

The Rust compiler needs to know the exact size of a type at compile time, but a recursive data structure's size is potentially unbounded.

For example, in the following, we define a linked list that has an unknown number of nodes, thus its size is not fixed. It could not be stored directly on the stack. By using Box, which has a defined size on the stack, we can create a local variable. The actual Node data will be stored on the heap.

//! Example using `Box<T>` for recursive data structures.

/// Represents a single element in a (highly simplified) linked list.
struct Node {
    /// The value stored in this node.
    value: i32,
    /// The next node in the list, or `None` if this is the last node.
    /// Note that `Node` is a recursive data type.
    next: Option<Box<Node>>,
}

impl Node {
    fn new(value: i32) -> Self {
        Node { value, next: None }
    }

    /// Recursively traverses the list until it finds the last node
    /// (where next is `None`) and sets its next field to a new `Node`.
    fn append(&mut self, value: i32) {
        match self.next {
            Some(ref mut next_node) => next_node.append(value),
            None => self.next = Some(Box::new(Node::new(value))),
        }
    }

    /// Prints the values of the nodes in the list, separated by " -> ".
    fn print(&self) {
        print!("{}", self.value);
        if let Some(ref next_node) = self.next {
            print!(" -> ");
            next_node.print();
        } else {
            println!();
        }
    }
}

// The linked list has an unknown number of nodes, thus its size is not
// fixed. It could not be stored directly on the stack, because the
// compiler needs to know the size of the data type at compile time.
// By using `Box`, which has a defined size on the stack, we can create the
// `head` local variable . The actual `Node` data will be stored on the heap.
fn main() {
    let mut head = Node::new(1);
    head.append(2);
    head.append(3);
    head.append(4);

    head.print(); // Output: 1 -> 2 -> 3 -> 4.
}

Related Topics

  • Memory Management.
  • Ownership & Borrowing.
  • Rust Patterns.