iTranslated by AI
Rust: Understanding Smart Pointers and Box<T>
Key Points
Overview
- Data can be stored on the heap instead of the stack
- What remains on the stack is a pointer to the heap data
Stack Heap
+-----------+ +-------------------+
| Box<T> | -----> | Data (Heap) |
| Pointer | | e.g., Large Struct|
+-----------+ +-------------------+
Use Cases
When you have a type whose size cannot be known at compile time and you want to use a value of that type in a context that requires an exact size
When you have a large amount of data and you want to transfer ownership but ensure the data is not copied
When you want to own a value and you care only that it’s a type that implements a particular trait rather than being a specific type
Example Usage (Obtaining Recursive Types with Known Sizes)
enum Node {
Cons(i32, Box<Node>),
Nil,
}
fn main() {
let list = Node::Cons(3,Box::new(Node::Cons(2,Box::new(Node::Cons(1,Box::new(Node::Nil))))),);
println!("Sum of list values: {}", sum(&list));
}
fn sum(list: &Node) -> i32 {
match list {
Node::Cons(value, next) => value + sum(next),
Node::Nil => 0,
}
}
What is Cons?
- A type of variant used in an enumeration (enum)
- A variant refers to the names of the specific cases or possibilities enumerated within an enum.
// For example, like this
enum Color {
Red,
Green,
Blue,
}
- It is used to hold "a single element (value) and a reference to the next element" in a list.
Cons(i32, Box<Node>) // Cons(value, reference to the next element)
Why use Box<T>?
Problem when not using it
enum Node {
Cons(i32, Node),
Nil,
}
To know the size of Node::Cons, the size of Node is required.
→ However, to know the size of Node, the size of Node::Cons is required recursively.
→ And since that Node contains another Node, the size calculation continues recursively and infinitely...
So, this is why we use Box<T>.
When using it
enum Node {
Cons(i32, Box<Node>), // Has 8 bytes in a 64-bit environment, 4 bytes in a 32-bit environment
Nil,
}
To know the size of Node::Cons, the size of Node is required.
→ However, the size of Box<Node> is fixed, so the size of Node does not grow recursively.
How to use Node
- It's nested, so it's a bit hard to follow, but the end looks like this:
Node::Cons(1, Box::new(Node::Nil)) // Meaning "1 is followed by Nil (the end)"
- It is stored in the list in a fashion like 3→2→1.
let list = Node::Cons(3,Box::new(Node::Cons(2,Box::new(Node::Cons(1,Box::new(Node::Nil))))),);
sum function
- Get the pointer of the list and check the content with a
matchexpression. - Separate cases for when the content is
Consand when it isNil. - In the case of
Cons, it returnsvalue + sum(next). Thesumfunction is called recursively. - Here,
nextis the reference to the next element inCons(value, reference to the next element). - In the case of
Nil, it returns0. - A calculation like
3 + (2 + (1 + 0)) = 6is performed.
fn sum(list: &Node) -> i32 {
match list {
Node::Cons(value, next) => value + sum(next),
Node::Nil => 0,
}
}
Discussion