- A pointer is a variable that stores the memory address, which points to any other data in the memory.
- Most common pointer is the reference (only simply borrow the value, they point to, i.e. doesn't have ownership of the value).
- References doesn't have any special capabilities, i.e. they doesn't have overhead (unlike pointers).
- Smart Pointers are data structures that act like pointers, but also have additional metadata and capabilities.
- In many cases, smart pointers have ownership of the data they point to (unlike references, which simply borrow values).
- Smart pointers are implemented using structs (they implement the
deref{:rust}&drop{:rust}traits). deref{:rust}trait allows an instance of the smart pointer struct to behave like a reference, so that you can write code that works with either references or smart pointers.drop{:rust}trait allows you to customize the code that is run when an instance of the smart pointer goes out of scope.
Box Smart Pointer
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}Box{:rust}is a smart pointer, it stores address of the heap memory, where the value5is stored.- Boxes doesn't have overhead, except storing data in the heap (but they have very limited capabilities)
Usage:
Boxes are useful when you have a type whose size can't be known at compile time and you want to use a value of that type in a context that requires an exact size.
Example:
enum List {
Cons(i32, List),
Nil,
}
use List::{ Cons, Nil };
fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));
}recursive type List{:rust} has infinite size2 | Cons(i32, List), | ---- recursive without indirectionCons(){:rust}constructs memory objects which hold two values or pointers to two values.
Fix:
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{ Cons, Nil };
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}Box{:rust}instead of calculating the size of the type at compile time, it stores the address of the heap memory, where the value is stored.
Deref Trait
deref{:rust}trait allows an instance of the smart pointer struct to behave like a reference, so that you can write code that works with either references or smart pointers.
fn main() {
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, y);
}Error: no implementation for {integer} == &{integer}{:rust}fn main() {
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y);
}*y{:rust}dereferences the referencey{:rust}to get the value it points to.
Deref Trait with Smart Pointers:
fn main() {
let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}Box{:rust}smart pointer implements thederef{:rust}trait, which allows it to be dereferenced like a reference.- Here
*y{:rust}dereferences theBox{:rust}smart pointer, to get the value from the address storing the duplicate value stored in heap.
Custom Smart Pointer
- Implementing the
Deref{:rust}trait on your own data structures allows you to use all the methods provided by the standard library.
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}Implicit Deref Coercions
- Implicit Deref Coercion is a convenience that Rust performs on arguments to functions and methods which implement the
Deref{:rust}trait. - It is used to convert a reference of one type into a reference of another type.
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m);
hello(&(*m)[..]);
}
fn hello(name: &str) {
println!("Hello, {}!", name);
}- Implicit Deref Coercion:
&MyBox<String>{:rust}->&String{:rust}->&str{:rust} &(*m){:rust}dereferences theMyBox<String>{:rust}, then & creates a reference to the String, and[..]{:rust}gets a slice ofString{:rust}.
Rust cannot perform deref coercion: \ \ From&T{:rust}(immutable reference) to&mut U{:rust}(mutable reference)
Drop Trait
drop{:rust}trait allows you to customize the code that is run when an instance of the smart pointer goes out of scope.- It automatically runs when the value goes out of scope.
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer { data: String::from("my stuff") };
let d = CustomSmartPointer { data: String::from("other stuff") };
println!("CustomSmartPointers created.");
drop(d);
println!("CustomSmartPointer dropped before the end of main.");
}CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
CustomSmartPointer dropped before the end of main.
Dropping CustomSmartPointer with data `my stuff`!