Treating Smart Pointers Like Regular References with Deref
The Deref
trait customizes the dereference operator (*
) behavior, allowing smart pointers to behave like regular references.
Basic Dereferencing
fn main() { let x = 5; let y = &x; assert_eq!(5, x); assert_eq!(5, *y); }
Using Box<T>
Like a Reference
fn main() { let x = 5; let y = Box::new(x); assert_eq!(5, x); assert_eq!(5, *y); }
Box<T>
can be dereferenced like a regular reference due to its Deref
implementation.
Implementing Custom Smart Pointers
struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } fn main() {}
This MyBox<T>
type won’t work with the dereference operator without implementing Deref
:
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
Implementing the Deref
Trait
use std::ops::Deref; impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } fn main() { let x = 5; let y = MyBox::new(x); assert_eq!(5, x); assert_eq!(5, *y); }
The deref
method returns a reference to the inner data. When you write *y
, Rust actually executes *(y.deref())
.
Deref Coercion
Deref coercion automatically converts references to types implementing Deref
into references to other types. This enables seamless interaction between different smart pointer types.
fn hello(name: &str) { println!("Hello, {name}!"); } fn main() {}
Calling a function expecting &str
with &MyBox<String>
:
use std::ops::Deref; impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } } struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } fn hello(name: &str) { println!("Hello, {name}!"); } fn main() { let m = MyBox::new(String::from("Rust")); hello(&m); }
Rust automatically applies deref coercion: &MyBox<String>
→ &String
→ &str
.
Without deref coercion, you’d need explicit conversions:
use std::ops::Deref; impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } } struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } fn hello(name: &str) { println!("Hello, {name}!"); } fn main() { let m = MyBox::new(String::from("Rust")); hello(&(*m)[..]); }
Deref Coercion Rules
Rust performs deref coercion in three cases:
&T
to&U
whenT: Deref<Target=U>
&mut T
to&mut U
whenT: DerefMut<Target=U>
&mut T
to&U
whenT: Deref<Target=U>
Note: Immutable references cannot coerce to mutable references due to borrowing rules.