Rust by Example: Smart Pointers Overview
Rust 1.75+
Unlock advanced memory management capabilities with Smart Pointers. This overview introduces the concept of types that act like pointers but have additional metadata, covering the Deref and Drop traits.
Code
use std::ops::Deref;
// A custom smart pointer
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
// Implement Deref to treat MyBox<T> like &T
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
// Implement Drop to run code on cleanup
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
// --- Deref Coercion ---
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y); // *y works because of Deref trait
println!("Deref works: {}", *y);
// --- Drop Trait ---
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");
// Variables are dropped in reverse order of creation
// d is dropped, then c is dropped
}Explanation
Smart Pointers are data structures that act like pointers but have additional metadata and capabilities. Unlike references, smart pointers usually own the data they point to. The most common examples are Box<T>, Rc<T>, and RefCell<T>.
Smart pointers are typically structs that implement two key traits:
- Deref: Allows an instance of the smart pointer to behave like a reference (using the
*operator). This enables Deref Coercion, where Rust automatically converts a reference to a smart pointer into a reference to the inner value (e.g.,&Box<String>becomes&str). - Drop: Allows you to customize what happens when a value goes out of scope. This is crucial for RAII (Resource Acquisition Is Initialization), ensuring resources like heap memory or file handles are released automatically.
Code Breakdown
13
impl Deref for MyBox. By implementing this trait, we define what happens when the * operator is used. We return a reference to the inner value &self.0.27
fn drop(&mut self). This method is called automatically when the variable goes out of scope. It's the destructor. Note that you cannot call c.drop() explicitly; if you need to drop early, use std::mem::drop(c).38
assert_eq!(5, *y);. Behind the scenes, Rust runs *(y.deref()). This allows us to treat MyBox transparently as if it were a regular reference.
