🎭Deref Trait

What is Deref?

💡 Deref is a trait that defines how to dereference a type. Dereferencing means accessing the underlying value that a reference points to.

💡 The Deref trait allows an instance of smart pointer to behave like a regular reference to the value it contains. By implementing the Deref trait, you can customize the behavior of the dereference operator to work with your custom types, enabling them to be used in contexts where references are expected.

The Deref trait is part of Rust's standard library and is defined as follows:

pub trait Deref {
    type Target: ?Sized;

    fn deref(&self) -> &Self::Target;
}

It contains one property and one required method:

  • type Target: ?Sized: This defines an associated type named Target within the trait which represents the type that we want to dereference to. The Target type can be any type, and it can also be a trait itself. The ?Sized part indicates that the size of the target type might not be known at compile time.

  • fn deref(&self) -> &Self::Target: This defines the required method of the trait named deref. It takes self (a reference to the implementing type) as an argument and returns a reference (&) to the associated type Target.

How Deref Is Used?

  • Implicit Dereferencing: The compiler often performs implicit dereferencing when using references. For example, if you have a reference to a variable (&x), you can directly use x in many situations, and the compiler will automatically dereference it to access the actual value.

  • Explicit Dereferencing: You can also explicitly dereference a reference using the dereference operator (*). This is useful when the compiler can't perform implicit dereferencing, or when you want to be more explicit about your code.

How Can We Use Deref ?

The Deref trait solves the issue of working with different types of references in a consistent way.

  • Unifying Reference Types: Rust has different types of references like &T (immutable reference), &mut T (mutable reference), and Box<T> (heap-allocated value). The Deref trait allows you to define a common way to access the underlying value for all these reference types. This simplifies code that needs to work with various reference types.

  • Custom Dereferencing Behavior: You can implement the Deref trait for custom types. This allows you to define how your custom type should be dereferenced.

Implicit Dereferencing

This is the most common way Deref is used. The compiler automatically dereferences a reference when it's used in certain contexts.

fn print_value(x: &i32) {
  println!("The value is: {}", x); // Implicit dereferencing of x
}

let value = 10;
print_value(&value);

In this example, the print_value function takes a reference to an i32 (&i32).

Inside the function, we can directly use x within the println! macro because the compiler implicitly dereferences x to access the underlying value.

Explicit Dereferencing

You can also explicitly dereference a reference using the dereference operator (*). This is useful when implicit dereferencing isn't allowed or for clarity:

fn try_swap(x: &mut i32, y: &mut i32) {
    // Explicit dereferencing needed to modify the borrowed values
    let temp = *x;
    *x = *y;
    *y = temp;
}

fn main() {
    let mut x = 5;
    let mut y = 10;
    
    println!("Before swxp: x = {}, y = {}", x, y);
    try_swap(&mut x, &mut y);
    println!("After swap: x = {}, y = {}", x, y);
}

In this example, The try_swap function takes two mutable references (&mut i32) as arguments, indicating it borrows both values for modification. This function will try to swap the value of x and y.

We create a temporary variable temp to hold the value of x before overwriting it.

Since we have mutable references (&mut i32), we need to explicitly dereference them using * to access and modify the underlying values.

This let temp = *x line, we dereference x to get its value and stores it in temp.

After that we assign (x = *y) the dereferenced value of y (using y) to the location pointed to by x (achieved through dereferencing x).

And the last line, we assign (y = temp) the value stored in temp (which is the original value of x) to the location pointed to by y (again, dereferencing y).

The main function would return swapped value between x and y.

Before swxp: x = 5, y = 10
After swap: x = 10, y = 5

Without explicit dereferencing, attempting to assign values directly to x and y within the function would result in a compile error because Rust's borrowing rules require explicit access to the underlying mutable values.

Using Deref with Box<T>

The Box<T> type implements Deref, allowing it to be used as a reference to T:

fn main() {
    let x = Box::new(5);
    println!("x = {}", x); // Outputs: 5 (a reference to the value of 5)
    println!("x = {}", *x); // Outputs: 5 (actual value)

    // assert_eq!(5, x); // ERROR
    assert_eq!(5, *x); // WORKED
}

In this example, we declare a variable x which holds a Box<i32> of value 5.

We print the value of x twice, one is x itself, and the other is a *x (dereference operator) which calls the deref method on the Box.

We can see that both outputs return the same value 5, however the first line returns a reference to the value 5 instead of actual value. And, the second line returns the actual value. Because the dereference operator allows us to access the underlying value.

For clarifying the value of x, we use 2 assertions. The first one asserts that x is equal 5. This will cause an ERROR, because 5 is an i32 type and the x is a reference of i32. Rust doesn’t allow to compare a number and a reference to a number because they’re different types.

To compare those values, we must use the dereference operator to follow the reference to the value it’s pointing to. In the second assertion, we have to use *x to follow the reference to the value of x (5), so the compiler can compare the actual value.

Using Deref with Custom Smart Pointer

In this example, we’re going to create a our own smart pointer which is called MySmartPointer<T> that implements Deref to allow dereferencing to the inner value T.

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 main() {
    let x = MySmartPointer::new(5);

    println!("x = {:?}", x); // Outputs: x = MySmartPointer(5)
    println!("x = {}", *x); // Outputs: x = 5
}

We create a MySmartPointer struct which wraps a value of type T .

After that we implement a new function for MySmartPointer instance. It’s taking an argument x of type T and storing it in the internal field.

To allow dereference for our smart point, we’re going to implement Deref trait for MySmartPointer. The deref method returns a reference (&) to the inner value (&self.0 - the .0 accesses the first value in a tuple struct). This allows dereferencing the smart pointer to access the underlying data.

In the main function, we create a MySmartPointer instance with value 5.

In the first line, we directly print the MySmartPointer instance which returns the instance of MySmartPointer with the value 5 (x = MySmartPointer(5) ).

To print the actual value, in the second line, we explicitly dereference the smart point using dereference operator (*x) to access the underlying value.

When we entered the *x , behind the scenes Rust actually ran this code:

(x.deref())

Last updated