Rust - Boxing

Rust has the concept of boxing, which enables storing values on the heap instead of the stack. Boxing in Rust is represented by the type Box<T>, which wraps a value of type T in a box object and manages the memory dynamically on the heap. This is particularly useful when the size of the type is not known at compile time or with recursive data types.

Bernd Fischer

Bernd Fischer

14. Oct 2024 · 3 min read

Rust - Boxing

Boxing is an important tool in Rust for storing values on the heap and managing dynamic data types. Whether for recursive data structures, polymorphic types, or to relieve the stack — Box offers an easy way to use memory efficiently while adhering to Rust's characteristic safety guarantees.

1. What is Boxing?

In Rust, most data is stored on the stack by default because this is very efficient. However, the stack has a fixed size and is limited. The heap, on the other hand, is more dynamic and can handle larger quantities of data. Boxing enables moving data onto the heap by wrapping it in a box.

The type Box<T> represents a pointer to a value of type T stored on the heap. The size of the box itself is known at compile time because it only contains the address of the stored value.

Example: Simple Boxing

fn main() {
    let x = 5;
    let boxed_x = Box::new(x); // x wird auf den Heap verschoben
    println!("x: {}, boxed_x: {}", x, boxed_x);
}

In this example, the variable x with the value 5 is created on the stack. However, the box boxed_x contains a copy of this value that is now stored on the heap.


2. When should you use Boxing?

Boxing is useful in Rust in various situations:

  1. When the size of the type is not known at compile time:
  2. Some types, especially recursive structures like trees or lists, have a dynamic size. In such cases, Box<T> can be used to ensure the value is stored on the heap even if its size is not statically known.

  3. Memory optimization:

  4. When working with large data structures or in systems with limited stack memory, it can make sense to move this data to the heap.

  5. Use of dynamic types:

  6. If polymorphic types or dynamic dispatch are needed, Box<dyn Trait> can be used to refer to an implementation of a trait.

3. Working with Box in Rust

Boxing in Rust is easy to use and can be applied in various ways.

Example: Moving values to the heap

To box a value, you can use the method Box::new():

fn main() {
    let my_value = Box::new(42); // Box<T> speichert den Wert 42 auf dem Heap
    println!("Boxed value: {}", my_value);
}

Here, the value 42 is stored on the heap, and the box itself is placed on the stack.

Dereferencing a Box

To get the value out of the box, use the dereference operator (*), similar to raw pointers.

fn main() {
    let boxed_value = Box::new(100);
    println!("Boxed value: {}", *boxed_value); // Dereferenzierung der Box
}

Dereferencing returns the value stored on the heap.


4. Recursive data types and Boxing

In Rust, recursive data types cannot be created directly without boxing, because their size is not fixed at compile time. However, with Box<T>, it is ensured that the memory for recursive data structures is managed dynamically.

Example: Recursive List

Here is an example of a recursive list implemented with Box<T>:

enum List {
    Cons(i32, Box<List>), // Kons-Struktur mit einem Wert und einer Box auf den nächsten Knoten
    Nil,                  // Ende der Liste
}

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

    println!("Liste erstellt!");
}

In this example, the list List consists of a value (i32) and a box pointing to the next list node. The end of the list is marked by Nil. Because the list is recursive, its size cannot be determined at compile time, so Box<T> is used.


5. Dynamic types with Box<dyn Trait>

In Rust, traits (similar to interfaces in other languages) can be used as dynamic types. Using Box<dyn Trait>, a value implementing a certain trait can be stored on the heap.

Example: Trait objects with Box<dyn Trait>

trait Animal {
    fn speak(&self);
}

struct Dog;

impl Animal for Dog {
    fn speak(&self) {
        println!("Woof!");
    }
}

fn main() {
    let my_dog: Box<dyn Animal> = Box::new(Dog);
    my_dog.speak(); // Dynamischer Dispatch
}

In this example, the type Dog implements the trait Animal. Using Box<dyn Animal>, the value Dog can be stored on the heap and dynamically accessed through its speak method. This allows runtime differentiation between various implementations of the trait.


6. Memory release with Box

One of Rust's great strengths is the ownership model. When a box goes out of scope, the value stored on the heap is automatically freed without manual memory management.

Example: Automatic memory release

fn main() {
    {
        let heap_value = Box::new(10);
        println!("Heap value: {}", heap_value);
    } // Hier wird `heap_value` automatisch freigegeben
    println!("heap_value ist jetzt aus dem Scope entfernt.");
}

Once heap_value leaves the scope, the heap memory is automatically released.