Deref Coercion
Deref coercion simplifies the way we interact with nested or wrapped data structures by allowing an instance of one type to behave like an instance of another type. This mechanism is enabled by implementing the Deref
trait, which allows implicit conversion (or coercion) to a different type, providing direct access to the underlying data.
Note: For now, deref coercion allows you to access a member of a type
T
as if it was a typeK
, but will not allow you to call functions whoseself
argument is of the original type when holding an instance of the coerced type.
Deref coercion is implemented via the Deref
and DerefMut
traits. When a type T
implements Deref
or DerefMut
to type K
, instances of T
can access the members of K
directly.
The Deref
trait in Cairo is defined as follows:
pub trait Deref<T> {
type Target;
fn deref(self: T) -> Self::Target;
}
pub trait DerefMut<T> {
type Target;
fn deref_mut(ref self: T) -> Self::Target;
}
The Target
type specifies the result of dereferencing, and the deref
method defines how to transform T
into K
.
Using Deref Coercion
To better understand how deref coercion works, let's look at a practical example. We'll create a simple generic wrapper type around a type T
called Wrapper<T>
, and use it to wrap a UserProfile
struct.
#[derive(Drop, Copy)]
struct UserProfile {
username: felt252,
email: felt252,
age: u16,
}
#[derive(Drop, Copy)]
struct Wrapper<T> {
value: T,
}
impl DerefWrapper<T> of Deref<Wrapper<T>> {
type Target = T;
fn deref(self: Wrapper<T>) -> T {
self.value
}
}
fn main() {
let wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }
};
// Access fields directly via deref coercion
println!("Username: {}", wrapped_profile.username);
println!("Current age: {}", wrapped_profile.age);
}
The Wrapper
struct wraps a single value generic of type T
. To simplify access to the wrapped value, we implement the Deref
trait for Wrapper<T>
.
#[derive(Drop, Copy)]
struct UserProfile {
username: felt252,
email: felt252,
age: u16,
}
#[derive(Drop, Copy)]
struct Wrapper<T> {
value: T,
}
impl DerefWrapper<T> of Deref<Wrapper<T>> {
type Target = T;
fn deref(self: Wrapper<T>) -> T {
self.value
}
}
fn main() {
let wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }
};
// Access fields directly via deref coercion
println!("Username: {}", wrapped_profile.username);
println!("Current age: {}", wrapped_profile.age);
}
This implementation is quite simple. The deref
method returns the wrapped value, allowing instances of Wrapper<T>
to access the members of T
directly.
In practice, this mechanism is totally transparent. The following example demonstrates how, holding an instance of Wrapper<UserProfile>
, we can print the username
and age
fields of the underlying UserProfile
instance.
#[derive(Drop, Copy)]
struct UserProfile {
username: felt252,
email: felt252,
age: u16,
}
#[derive(Drop, Copy)]
struct Wrapper<T> {
value: T,
}
impl DerefWrapper<T> of Deref<Wrapper<T>> {
type Target = T;
fn deref(self: Wrapper<T>) -> T {
self.value
}
}
fn main() {
let wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }
};
// Access fields directly via deref coercion
println!("Username: {}", wrapped_profile.username);
println!("Current age: {}", wrapped_profile.age);
}
Restricting Deref Coercion to Mutable Variables
While Deref
works for both mutable and immutable variables, DerefMut
will only be applicable to mutable variables. Contrary to what the name might suggest, DerefMut
does not provide mutable access to the underlying data.
//TAG: does_not_compile
use core::ops::DerefMut;
#[derive(Drop, Copy)]
struct UserProfile {
username: felt252,
email: felt252,
age: u16,
}
#[derive(Drop, Copy)]
struct Wrapper<T> {
value: T,
}
impl DerefMutWrapper<T, +Copy<T>> of DerefMut<Wrapper<T>> {
type Target = T;
fn deref_mut(ref self: Wrapper<T>) -> T {
self.value
}
}
fn error() {
let wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }
};
// Uncommenting the next line will cause a compilation error
println!("Username: {}", wrapped_profile.username);
}
fn main() {
let mut wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }
};
println!("Username: {}", wrapped_profile.username);
println!("Current age: {}", wrapped_profile.age);
}
If you try to use DerefMut
with an immutable variable, the compiler would throw an error. Here’s an example:
//TAG: does_not_compile
use core::ops::DerefMut;
#[derive(Drop, Copy)]
struct UserProfile {
username: felt252,
email: felt252,
age: u16,
}
#[derive(Drop, Copy)]
struct Wrapper<T> {
value: T,
}
impl DerefMutWrapper<T, +Copy<T>> of DerefMut<Wrapper<T>> {
type Target = T;
fn deref_mut(ref self: Wrapper<T>) -> T {
self.value
}
}
fn error() {
let wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }
};
// Uncommenting the next line will cause a compilation error
println!("Username: {}", wrapped_profile.username);
}
fn main() {
let mut wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }
};
println!("Username: {}", wrapped_profile.username);
println!("Current age: {}", wrapped_profile.age);
}
Compiling this code will result in the following error:
$ scarb build
Compiling no_listing_09_deref_coercion_example v0.1.0 (listings/ch11-advanced-features/no_listing_09_deref_mut_example/Scarb.toml)
error: Type "no_listing_09_deref_coercion_example::Wrapper::<no_listing_09_deref_coercion_example::UserProfile>" has no member "username"
--> listings/ch11-advanced-features/no_listing_09_deref_mut_example/src/lib.cairo:32:46
println!("Username: {}", wrapped_profile.username);
^******^
error: could not compile `no_listing_09_deref_coercion_example` due to previous error
For the above code to work, we need to define wrapped_profile
as a mutable variable.
//TAG: does_not_compile
use core::ops::DerefMut;
#[derive(Drop, Copy)]
struct UserProfile {
username: felt252,
email: felt252,
age: u16,
}
#[derive(Drop, Copy)]
struct Wrapper<T> {
value: T,
}
impl DerefMutWrapper<T, +Copy<T>> of DerefMut<Wrapper<T>> {
type Target = T;
fn deref_mut(ref self: Wrapper<T>) -> T {
self.value
}
}
fn error() {
let wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }
};
// Uncommenting the next line will cause a compilation error
println!("Username: {}", wrapped_profile.username);
}
fn main() {
let mut wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }
};
println!("Username: {}", wrapped_profile.username);
println!("Current age: {}", wrapped_profile.age);
}
Summary
By using the Deref
and DerefMut
traits, we can transparently convert one type into another, simplifying the access to nested or wrapped data structures. This feature is particularly useful when working with generic types or building abstractions that require seamless access to the underlying data and can help reduce boilerplate code. However, this functionality is quite limited, as you cannot call functions whose self
argument is of the original type when holding an instance of the coerced type.