DEV Community

Cover image for [Rust Guide] 15.2 What Is the `Deref` Trait, the Dereference Operator, and Implementing the `Deref` Trait
SomeB1oody
SomeB1oody

Posted on • Edited on • Originally published at someb1oody.github.io

[Rust Guide] 15.2 What Is the `Deref` Trait, the Dereference Operator, and Implementing the `Deref` Trait

15.2.1 What Is the Deref Trait

Deref is short for Dereference.

If a type implements the Deref trait, it allows us to customize the behavior of the dereference operator *. By implementing Deref, a smart pointer can be handled like a regular reference.

15.2.2 The Dereference Operator

First, let’s emphasize that a regular reference is also a kind of pointer. Take a look at this example:

fn main(){
    let x = 5;
    let y = &x;

    assert_eq!(x, 5);
    assert_eq!(*y, 5);
}
Enter fullscreen mode Exit fullscreen mode
  • x is of type i32 and holds the value 5; y stores a reference that points to the memory address of x. Its type is &i32, which means y is a reference to x.
  • The first assertion compares x with 5. Since the value stored in x is 5, the two are equal, so the assertion passes.
  • The second assertion compares *y with 5. y is a pointer, and if you want to extract the value it points to, you add the dereference symbol * in front of the variable name. In other words, the type of y is &i32, the type of *y is i32, and because 5 is also of type i32, *y can be compared with 5, while y cannot.

15.2.3 Using Box<T> as a Reference

Box<T> can replace the reference in the previous example. Take a look:

fn main(){
    let x = 5;
    let y = Box::new(x);

    assert_eq!(x, 5);
    assert_eq!(*y, 5);
}
Enter fullscreen mode Exit fullscreen mode

It is worth noting that the logic in the previous code and this code is slightly different:

  • y = &x in the previous example assigns a pointer to x to y, which is a pointer to stack memory, because i32 is stored on the stack.
  • y = Box::new(x) here copies the value of x to the heap and then passes the pointer to that heap value to y.

15.2.4 Defining Your Own Smart Pointer

Box<T> is defined as a tuple struct with one element (for tuple structs, see 5.1. Defining and Instantiating Structs). Let’s define a MyBox<T>, which is also a tuple struct:

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}
Enter fullscreen mode Exit fullscreen mode
  • First we define a tuple struct MyBox, using the generic parameter T to stand in for the concrete type, and store a value of type T in this tuple struct.
  • Then, through an impl block, we define a new function to create a new MyBox instance.

Now let’s write the main function and see whether it works in practice:

fn main(){
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(x, 5);
    assert_eq!(*y, 5);
}
Enter fullscreen mode Exit fullscreen mode

The final assertion, assert_eq!(*y, 5), produces an error at *y. The error message is:

error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
  --> src/main.rs:14:13
   |
14 |     assert_eq!(*y, 5);
   |                ^^ can't be dereferenced

For more information about this error, try `rustc --explain E0614`.
error: could not compile `mybox` (bin "mybox") due to 1 previous error
Enter fullscreen mode Exit fullscreen mode

That means MyBox cannot be dereferenced.

This is because we have not implemented the Deref trait for MyBox.

15.2.5 Implementing the Deref Trait

The Deref trait in the standard library requires us to implement a deref method: this method borrows self and returns a reference to the internal data.

Using the code above as an example, if we want to implement the Deref trait for MyBox—that is, implement the deref method—we can write:

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.0
    }
}
Enter fullscreen mode Exit fullscreen mode
  • use std::ops::Deref; brings the Deref trait into the current scope.
  • To implement Deref for MyBox, we write impl<T> Deref for MyBox<T> and then implement the deref method in that impl block.
  • type Target = T; defines the associated type of the Deref trait. Associated types are a slightly different way to define generic parameters, and we will talk about them later.
  • The deref method borrows self, that is, &self, and returns a reference of type &T, specifically &self.0: it returns the element at index 0 in the tuple struct by reference (in this case, there is only one element). Since the return value is a reference, we can use the * dereference operator to access the value.

Let’s run the main function again and see whether there is any problem:

fn main(){
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(x, 5);
    assert_eq!(*y, 5);
}
Enter fullscreen mode Exit fullscreen mode

It compiles successfully, so there is no problem.

In fact, the Rust compiler implicitly expands *y in main into:

*(y.deref())
Enter fullscreen mode Exit fullscreen mode

It first calls the deref method on MyBox to return a reference, and then uses the dereference operator * for an ordinary dereference operation.

Top comments (0)