π§Deref Coercion
What is Deref Coercion?
Deref Coercion?π‘ The Deref trait enables "Deref coercion," which allows the compiler to automatically perform implicit dereferencing when calling functions or methods by converting the references of custom types to references of their inner types.
This is how Deref coercion works behind the scenes:
The compiler will check if the argument type implements
Deref.If it does, and the dereferenced type matches the expected type in the function signature, the compiler will automatically inserts calls to the
derefmethod as needed.
When the Deref Coercion is applied?
Deref Coercion is applied?Rust performs Deref coercion in three specific cases:
From &T to &U when T: Deref<Target=U>
This happens when you pass a reference to a type
T(e.g.,&String) to a function or method that expects a reference to a different typeU(e.g.,&str).
As long as
TimplementsDeref<Target=U>, the compiler will automatically dereference the&Treference to get a reference to the underlyingUvalue.
This allows you to use types like
Stringinterchangeably with&strin many situations becauseStringimplementsDeref<Target=str>.
From &mut T to &mut U when T: DerefMut<Target=U>
This case is similar to the first one, but it applies to mutable references (
&mut T).
Derefcoercion occurs when you pass a mutable reference to a typeTto a function or method that expects a mutable reference to a different typeU.
As long as
TimplementsDerefMut<Target=U>, the compiler will dereference the&mut Tto provide a mutable reference to the underlyingUvalue.
This allows you to use types like
Box<T>(heap-allocated box) interchangeably with&mut Tin some contexts when necessary (assumingBox<T>implementsDerefMut<Target=T>).
From &mut T to &U when T: Deref<Target=U>
This case is less common, but it's still valid.
It allows dereferencing a mutable reference to a type
Tto get an immutable reference to a typeU.
Similar to the first case, the condition is that
TimplementsDeref<Target=U>. This can be useful in specific scenarios where you might need a temporary immutable reference from a mutable reference.
Deref with Function & Method Calls
Deref with Function & Method CallsUsing the previous example of our custom smart pointer. To demonstrate how the Deref coercion works, we add a new function say_hi which receives a &str reference.
use std::ops::Deref;
#[derive(Debug)]
struct MySmartPointer<T>(T);
impl<T> MySmartPointer<T> {
fn new(x: T) -> MySmartPointer<T> {
MySmartPointer(x)
}
}
impl<T> Deref for MySmartPointer<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
fn say_hi(name: &str) {
println!("Hi, {name}!")
}
fn main() {
let name = MySmartPointer::new(String::from("Thomas"));
println!("name = {:?}", name); // Outputs: name = MySmartPointer(5)
println!("name = {}", *name); // Outputs: name = 5
say_hi(&name); // Outputs: Hi, Thomas!
}
In this code, the Deref coercion happens in the say_hi function.
The say_hi function expects a &str reference. But we passed &name, which is a reference to a MySmartPointer<String>.
Deref coercion comes into play again:
The compiler sees the argument type
&MySmartPointer<String>and the expected type&str.MySmartPointer<String>implementsDeref<Target=String>, butsay_hineeds&strBehind the scenes, the compiler performs a double dereference.
First, it dereferences
&nameto get a reference&Stringfrom inner&MySmartPointer<String>.Then, because
StringimplementsDeref<Target=str>, it automatically dereferences again to the&Stringto get the underlying string slice (&str) thatsay_hican use.Finally, the
say_hifunction will get the correct string format and printHi, Thomas!.
DerefMut for Mutable Dereferencing
DerefMut for Mutable DereferencingRust also provides the DerefMut trait for mutable dereferencing.
Letβs dive a bit inside the Deref implementation
pub trait DerefMut: Deref<Target = Self::Target> {
fn deref_mut(&mut self) -> &mut Self::Target;
}DerefMut trait inherits from the Deref trait, it means it requires everything from Deref and adds its own method.
The different here is the DerefMut has a required method which is called deref_mut. This method takes &mut self (a mutable reference to the implementing type) as an argument and returns a mutable reference (&mut) to the associated type Target.
As the previous example, we have the MySmartPointer smart pointer with implemented Deref trait. In this updates, we will implement the DerefMut trait for MySmartPointer to demonstrate how we can dereference a mutable reference and mutate the data.
use std::ops::{Deref, DerefMut};
#[derive(Debug)]
struct MySmartPointer<T>(T);
impl<T> MySmartPointer<T> {
fn new(x: T) -> MySmartPointer<T> {
MySmartPointer(x)
}
}
impl<T> Deref for MySmartPointer<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for MySmartPointer<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
fn say_hi(name: &str) {
println!("Hi, {name}!")
}
fn main() {
let mut name = MySmartPointer::new(String::from("Thomas"));
*name = String::from("Ashley"); // DerefMut happens here
say_hi(&name); // Outputs: Hi, Ashley!
}To allow dereference mutable reference for our smart pointer, weβre going to implement DerefMut trait for MySmartPointer. The deref_mut method returns a mutable reference (&mut) to the inner value (&self.0 - the .0 accesses the first value in a tuple struct). This allows dereferencing and mutating to the underlying data.
In the main function, we created a new mutable MySmartPointer with the string βThomasβ.
The DerefMut coercion happens in the name = String::from("Ashley") line.
Weβre assigning a new
String("Ashley") to the dereferenced value ofname.Derefcoercion happens becauseMySmartPointerimplementsDerefMut<Target=String>.The compiler automatically dereferences
name(which is a&mut MySmartPointer<String>) usingderef_mutto get a mutable reference (&mut String) to the inner value, and then modifies the inner string through assignment*name.Finally, the
say_hifunction will printHi, Ashley!.
Last updated