Deref and DerefMut

Deref is the trait that lets you use * to dereference something. We saw the word Deref before when using a tuple struct to make a new type, and now it's time to learn it.

We know that a reference is not the same as a value:

// ⚠️
fn main() {
    let value = 7; // This is an i32
    let reference = &7; // This is a &i32
    println!("{}", value == reference);
}

And Rust won't even give a false because it won't even compare the two.

error[E0277]: can't compare `{integer}` with `&{integer}`
 --> src\main.rs:4:26
  |
4 |     println!("{}", value == reference);
  |                          ^^ no implementation for `{integer} == &{integer}`

Of course, the solution here is *. So this will print true:

fn main() {
    let value = 7;
    let reference = &7;
    println!("{}", value == *reference);
}

Now let's imagine a simple type that just holds a number. It will be like a Box, and we have some ideas for some extra functions for it. But if we just give it a number, it won't be able to do much with it.

We can't use * like we can with Box:

// ⚠️
struct HoldsANumber(u8);

fn main() {
    let my_number = HoldsANumber(20);
    println!("{}", *my_number + 20);
}

The error is:

error[E0614]: type `HoldsANumber` cannot be dereferenced
  --> src\main.rs:24:22
   |
24 |     println!("{:?}", *my_number + 20);

We can of course do this: println!("{:?}", my_number.0 + 20);. But then we are just adding a separate u8 to the 20. It would be nice if we could just add them together. The message cannot be dereferenced gives us a clue: we need to implement Deref. Something simple that implements Deref is sometimes called a "smart pointer". A smart pointer can point to its item, has information about it, and can use its methods. Because right now we can add my_number.0, which is a u8, but we can't do much else with a HoldsANumber: all it has so far is Debug.

Interesting fact: String is actually a smart pointer to &str and Vec is a smart pointer to array (or other types). So we have actually been using smart pointers since the beginning.

Implementing Deref is not too hard and the examples in the standard library are easy. Here's the sample code from the standard library:

use std::ops::Deref;

struct DerefExample<T> {
    value: T
}

impl<T> Deref for DerefExample<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

fn main() {
    let x = DerefExample { value: 'a' };
    assert_eq!('a', *x);
}

So we follow that and now our Deref looks like this:


#![allow(unused)]
fn main() {
// 🚧
impl Deref for HoldsANumber {
    type Target = u8; // Remember, this is the "associated type": the type that goes together.
                      // You have to use the right type Target = (the type you want to return)

    fn deref(&self) -> &Self::Target { // Rust calls .deref() when you use *. We just defined Target as a u8 so this is easy to understand
        &self.0   // We chose &self.0 because it's a tuple struct. In a named struct it would be something like "&self.number"
    }
}
}

So now we can do this with *:

use std::ops::Deref;
#[derive(Debug)]
struct HoldsANumber(u8);

impl Deref for HoldsANumber {
    type Target = u8;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn main() {
    let my_number = HoldsANumber(20);
    println!("{:?}", *my_number + 20);
}

So that will print 40 and we didn't need to write my_number.0. That means we get the methods of u8 and we can write our own methods for HoldsANumber. We will add our own simple method and use another method we get from u8 called .checked_sub(). The .checked_sub() method is a safe subtraction that returns an Option. If it can do the subtraction then it gives it to you inside Some, and if it can't do it then it gives a None. Remember, a u8 can't be negative so it's safer to do .checked_sub() so we don't panic.

use std::ops::Deref;

struct HoldsANumber(u8);

impl HoldsANumber {
    fn prints_the_number_times_two(&self) {
        println!("{}", self.0 * 2);
    }
}

impl Deref for HoldsANumber {
    type Target = u8;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn main() {
    let my_number = HoldsANumber(20);
    println!("{:?}", my_number.checked_sub(100)); // This method comes from u8
    my_number.prints_the_number_times_two(); // This is our own method
}

This prints:

None
40

We can also implement DerefMut so we can change the values through *. It looks almost the same. You need Deref before you can implement DerefMut.

use std::ops::{Deref, DerefMut};

struct HoldsANumber(u8);

impl HoldsANumber {
    fn prints_the_number_times_two(&self) {
        println!("{}", self.0 * 2);
    }
}

impl Deref for HoldsANumber {
    type Target = u8;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for HoldsANumber { // You don't need type Target = u8; here because it already knows thanks to Deref
    fn deref_mut(&mut self) -> &mut Self::Target { // Everything else is the same except it says mut everywhere
        &mut self.0
    }
}

fn main() {
    let mut my_number = HoldsANumber(20);
    *my_number = 30; // DerefMut lets us do this
    println!("{:?}", my_number.checked_sub(100));
    my_number.prints_the_number_times_two();
}

So you can see that Deref gives your type a lot of power.

This is also why the standard library says: Deref should only be implemented for smart pointers to avoid confusion. That's because you can do some strange things with Deref for a complicated type. Let's imagine a really confusing example to understand what they mean. We'll start with Character struct for a game. A new Character needs some stats like intelligence and strength. So here is our first character:

struct Character {
    name: String,
    strength: u8,
    dexterity: u8,
    health: u8,
    intelligence: u8,
    wisdom: u8,
    charm: u8,
    hit_points: i8,
    alignment: Alignment,
}

impl Character {
    fn new(
        name: String,
        strength: u8,
        dexterity: u8,
        health: u8,
        intelligence: u8,
        wisdom: u8,
        charm: u8,
        hit_points: i8,
        alignment: Alignment,
    ) -> Self {
        Self {
            name,
            strength,
            dexterity,
            health,
            intelligence,
            wisdom,
            charm,
            hit_points,
            alignment,
        }
    }
}

enum Alignment {
    Good,
    Neutral,
    Evil,
}

fn main() {
    let billy = Character::new("Billy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good);
}

Now let's imagine that we want to keep character hit points in a big vec. Maybe we'll put monster data in there too, and keep it all together. Since hit_points is an i8, we implement Deref so we can do all sorts of math on it. But look at how strange it looks in our main() function now:

use std::ops::Deref;

// All the other code is the same until after the enum Alignment
struct Character {
    name: String,
    strength: u8,
    dexterity: u8,
    health: u8,
    intelligence: u8,
    wisdom: u8,
    charm: u8,
    hit_points: i8,
    alignment: Alignment,
}

impl Character {
    fn new(
        name: String,
        strength: u8,
        dexterity: u8,
        health: u8,
        intelligence: u8,
        wisdom: u8,
        charm: u8,
        hit_points: i8,
        alignment: Alignment,
    ) -> Self {
        Self {
            name,
            strength,
            dexterity,
            health,
            intelligence,
            wisdom,
            charm,
            hit_points,
            alignment,
        }
    }
}

enum Alignment {
    Good,
    Neutral,
    Evil,
}

impl Deref for Character { // impl Deref for Character. Now we can do any integer math we want!
    type Target = i8;

    fn deref(&self) -> &Self::Target {
        &self.hit_points
    }
}



fn main() {
    let billy = Character::new("Billy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good); // Create two characters, billy and brandy
    let brandy = Character::new("Brandy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good);

    let mut hit_points_vec = vec![]; // Put our hit points data in here
    hit_points_vec.push(*billy);     // Push *billy?
    hit_points_vec.push(*brandy);    // Push *brandy?

    println!("{:?}", hit_points_vec);
}

This just prints [5, 5]. Our code is now very strange for someone to read. We can read Deref just above main() and figure out that *billy means i8, but what if there was a lot of code? Maybe our code is 2000 lines long, and suddenly we have to figure out why we are .push()ing *billy. Character is certainly more than just a smart pointer for i8.

Of course, it is not illegal to write hit_points_vec.push(*billy), but it makes the code look very strange. Probably a simple .get_hp() method would be much better, or another struct that holds the characters. Then you could iterate through and push the hit_points for each one. Deref gives a lot of power but it's good to make sure that the code is logical.