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:
- 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.
- When you want to transfer ownership of a large amount of data without copying it.
Base example:
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):
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 useArc<T>(Atomic Reference Counted).
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
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.
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)
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.
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
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.
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