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.
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
}
}
#[executable]
fn main() {
let wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: '[email protected]', 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
}
}
#[executable]
fn main() {
let wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: '[email protected]', 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
}
}
#[executable]
fn main() {
let wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: '[email protected]', 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: '[email protected]', age: 30 },
};
// Uncommenting the next line will cause a compilation error
println!("Username: {}", wrapped_profile.username);
}
#[executable]
fn main() {
let mut wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: '[email protected]', 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: '[email protected]', age: 30 },
};
// Uncommenting the next line will cause a compilation error
println!("Username: {}", wrapped_profile.username);
}
#[executable]
fn main() {
let mut wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: '[email protected]', 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/ch12-advanced-features/no_listing_09_deref_mut_example/Scarb.toml)
error[E0007]: Type "no_listing_09_deref_coercion_example::Wrapper::<no_listing_09_deref_coercion_example::UserProfile>" has no member "username"
--> listings/ch12-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 1 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: '[email protected]', age: 30 },
};
// Uncommenting the next line will cause a compilation error
println!("Username: {}", wrapped_profile.username);
}
#[executable]
fn main() {
let mut wrapped_profile = Wrapper {
value: UserProfile { username: 'john_doe', email: '[email protected]', age: 30 },
};
println!("Username: {}", wrapped_profile.username);
println!("Current age: {}", wrapped_profile.age);
}
Calling Methods via Deref Coercion
In addition to accessing members, deref coercion also allows calling methods defined on the target type directly on the source type instance. Let's illustrate this with an example:
struct MySource {
pub data: u8,
}
struct MyTarget {
pub data: u8,
}
#[generate_trait]
impl TargetImpl of TargetTrait {
fn foo(self: MyTarget) -> u8 {
self.data
}
}
impl SourceDeref of Deref<MySource> {
type Target = MyTarget;
fn deref(self: MySource) -> MyTarget {
MyTarget { data: self.data }
}
}
#[executable]
fn main() {
let source = MySource { data: 5 };
// Thanks to the Deref impl, we can call foo directly on MySource
let res = source.foo();
assert!(res == 5);
}
In this example, MySource implements Deref to MyTarget. The MyTarget
struct has an implementation TargetImpl of the trait TargetTrait which
defines a method foo. Because MySource dereferences to MyTarget, we can
call the foo method directly on an instance of MySource, as demonstrated in
the main function.
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
and enabling method calls defined on the target type. This feature is
particularly useful when working with generic types or building abstractions
that require easy access to the underlying data and can help reduce boilerplate
code.