Skip to main content
eLearner.app
Module 6 · Lesson 2 of 212/14 in the course~15 min
Module lessons (2/2)

Smart Pointers: Box and Rc

A smart pointer is a data structure that behaves like a pointer, but has metadata and extra capabilities (such as reference counting or automatic memory management).

In Rust, ordinary references (like &) only borrow data. Smart pointers, on the other hand, often own the data they point to. Common examples include String and Vec<T>, but the standard library provides other specialized ones.


Box<T> for Heap Allocation

The simplest smart pointer is Box<T>, which stores data on the heap rather than the stack. Only the pointer to the heap-allocated data remains on the stack.

It is mainly used in the following cases:

  1. When you have a type whose size cannot be known at compile time (like a recursive type), and you want to use a value of that type in a context that requires an exact size.
  2. When you want to transfer ownership of a large amount of data without copying it.

Base example:

Code
fn main() {
    let b = Box::new(5); // allocates the value 5 on the heap
    println!("b = {}", *b); // dereferences with * to read the value
}

Recursive Types and Cons Lists

A recursive type is a type that can contain a value of itself as part of its definition. Rust must know the exact size of a type at compile time. Since the depth of a recursive type could be infinite, the compiler generates an error unless you use a Box (which has a fixed size, since it is a pointer):

Code
enum List {
    Cons(i32, Box<List>),
    Nil,
}

Rc<T>: Reference Counting

There are cases where a single value can have multiple owners. The Rc<T> (Reference Counted) smart pointer keeps track of the number of references to a value to determine if the value is still in use. If the count drops to zero, the resource is safely deallocated.

[!NOTE] Rc<T> is only suitable for single-threaded scenarios. For multi-threaded (concurrent) scenarios, you must use Arc<T> (Atomic Reference Counted).

Code
use std::rc::Rc;

fn main() {
    let a = Rc::new(5);
    let b = Rc::clone(&a); // increments the reference count to 2
    println!("References: {}", Rc::strong_count(&a));
}

Try it yourself

Exercise 1: Heap Allocation with Box

Exercise#rust.m6.l2.e1
Attempts: 0Loading…

Allocate an integer value of 42 on the heap using Box::new and assign it to a variable named val. Next, print the value stored in the Box by explicitly dereferencing it with the * operator.

Loading editor…
Show hint

Use `let val = Box::new(42);` to store the value in heap and dereference it with `*val` inside `println!`.

Solution available after 3 attempts

Exercise 2: Recursive Data Structure (Cons List)

Exercise#rust.m6.l2.e2
Attempts: 0Loading…

Define a recursive enum named List containing two variants: Cons which wraps a tuple (i32, Box<List>), and Nil which represents the end of the list. In the main function, instantiate a linked list containing the element 1 followed by Nil.

Loading editor…
Show hint

The enum is declared as `enum List { Cons(i32, Box<List>), Nil }`. Instantiate the list with `List::Cons(1, Box::new(List::Nil))`.

Solution available after 3 attempts

Exercise 3: Sharing Ownership with Rc

Exercise#rust.m6.l2.e3
Attempts: 0Loading…

Use std::rc::Rc to create a shared integer containing the value 100 in a variable a. Clone the reference into a variable b using Rc::clone. Finally, print the count of active references using the Rc::strong_count function.

Loading editor…
Show hint

Create the value with `Rc::new(100)`. Clone with `Rc::clone(&a)`and display the owner count with`Rc::strong_count(&a)`.

Solution available after 3 attempts