Rust – Concept of Smart Pointers
Pointers are basically variables that store the address of another variable. While smart pointers are data types that behave like a traditional pointer with additional capabilities such as automatic memory management or bounds checking.
What makes smart pointers different from normal pointers are:-
- They are structs and contain additional metadata and capabilities.
- They do not borrow the data rather they own it.
What makes smart pointers different from structs:-
- They implement the Deref and Drop traits.
Deref trait allows an instance of a smart pointer struct to behave like a reference so that the code that works with pointers can also work with smart pointers.
Drop trait allows us to customize the code that should run when an instance of the smart pointer goes out of scope.
Some of the smart pointers are:-
- Box<T> to allocate values on the heap
- Rc<T> a reference counting type that enables multiple ownership
Box<T>
Box allows us to store data in the heap contrary to the default scheme of Rust to store as a stack.
Box is basically used:
- For dynamic allocation of memory for variables.
- When there is a lot of data that we need to transfer ownership, and we don’t want them to are copied.
Let’s create a box to store i32 value in a heap
Rust
fn main() { let num = Box::new(4); println!( "num = {}" , num); } |
Output
num = 4
Using Box<T> for recursive type
We will be using cons list to create a list of values. cons list takes to values the first one is the current value and the other is the next value, it performs a recursive call to cons function to generate a list, where the base condition for the recursive call is Nil.
Rust
enum List { Cons(i32, List), Nil, } use crate ::List::{Cons, Nil}; fn main() { let list = Cons(1, Cons(2, Cons(3, Nil))); } |
It won’t compile because the List variable size cannot be determined prior to compilation.
Output:
rustc -o main main.rs error[E0072]: recursive type `List` has infinite size --> main.rs:1:1 | 1 | enum List { | ^^^^^^^^^ recursive type has infinite size 2 | Cons(i32, List), | ---- recursive without indirection | = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `List` representable error[E0391]: cycle detected when processing `List` --> main.rs:1:1 | 1 | enum List { | ^^^^^^^^^ | = note: ...which again requires processing `List`, completing the cycle = note: cycle used when computing dropck types for `Canonical { max_universe: U0, variables: [], value: ParamEnvAnd { param_env: ParamEnv { caller_bounds: [], reveal: UserFacing, def_id: None }, value: List } }`
There is an error that tells the List has infinite size because the compiler cannot determine the size of List during compilation. So we will be using a pointer to the list rather than the list itself to overcome this error. Since the size of the pointer is fixed irrespective of the data type to which it is pointing to, therefore the compiler can determine its size during compilation. Let’s see this implementation using Box<T>
Rust
#[derive(Debug)] enum List { Cons(i32, Box<List>), Nil, } use crate ::List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); println!( "{:?}" ,list) } |
Output:
Cons(1, Cons(2, Cons(3, Nil)))
Defining our own smart pointer:
Rust
struct CustomPointer<T>(T); impl <T> CustomPointer<T> { fn new(x: T) -> CustomPointer<T> { CustomPointer(x) } } |
Here, we have defined a Custom smart pointer as a tuple struct with one value. We have defined a new method which is called when the smart pointer is instantiated, that returns a custom smart pointer.
Using Dereference trait on our custom smart pointer:
Just like an ordinary pointer, we can get the value of the box by using the ‘*’ operator.
Rust
struct CustomPointer<T>(T); impl <T> CustomPointer<T> { fn new(x: T) -> CustomPointer<T> { CustomPointer(x) } } //implementing deref trait use std::ops::Deref; impl <T> Deref for CustomPointer<T> { type Target = T; fn deref(& self ) -> & Self ::Target { & self .0 } } fn main() { let x = 5; let y = CustomPointer::new(x); println!( "{}" ,x==5); println!( "{}" ,*y==5); } |
Output:
true true
Using Drop trait on our smart pointer:
Whenever the instance of smart pointer goes out of scope then the drop function is called.
Rust
// defining custom smart pointer struct CustomPointer <T>(T); impl <T> CustomPointer<T> { fn new(x: T) -> CustomPointer<T> { CustomPointer(x) } } use std::ops::Deref; // for implementing deref trait impl <T> Deref for CustomPointer<T> { type Target = T; fn deref(& self ) -> & Self ::Target { & self .0 } } // for implementing drop trait impl <T> Drop for CustomPointer<T> { fn drop(& mut self ) { println!( "Dropping CustomtPointer with data" ); } } fn main() { let x = 5; let y = CustomPointer::new(x); println!( "{}" ,x==5); println!( "{}" ,*y==5); } |
Output:
true true Dropping CustomtPointer with data
Please Login to comment...