Let’s first look at how the dereference operator works with regular references. Then we’ll try to define a custom type that behaves like Box, and see why the dereference operator doesn’t work like a reference on our newly defined type. We’ll explore how implementing the Deref trait makes it possible for smart pointers to work in ways similar to references. Then we’ll look at Rust’s deref coercion feature and how it lets us work with either references or smart pointers.

    A regular reference is a type of pointer, and one way to think of a pointer is as an arrow to a value stored somewhere else. In Listing 15-6, we create a reference to an i32 value and then use the dereference operator to follow the reference to the value:

    Filename: src/main.rs

    Listing 15-6: Using the dereference operator to follow a reference to an i32 value

    The variable x holds an i32 value 5. We set y equal to a reference to x. We can assert that x is equal to 5. However, if we want to make an assertion about the value in y, we have to use *y to follow the reference to the value it’s pointing to (hence dereference) so the compiler can compare the actual value. Once we dereference y, we have access to the integer value y is pointing to that we can compare with 5.

    If we tried to write assert_eq!(5, y); instead, we would get this compilation error:

    1. $ cargo run
    2. Compiling deref-example v0.1.0 (file:///projects/deref-example)
    3. error[E0277]: can't compare `{integer}` with `&{integer}`
    4. --> src/main.rs:6:5
    5. |
    6. 6 | assert_eq!(5, y);
    7. | ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
    8. |
    9. = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}`
    10. = help: the following other types implement trait `PartialEq<Rhs>`:
    11. f32
    12. f64
    13. i128
    14. i16
    15. i32
    16. i64
    17. i8
    18. isize
    19. and 6 others
    20. = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
    21. For more information about this error, try `rustc --explain E0277`.
    22. error: could not compile `deref-example` due to previous error

    Comparing a number and a reference to a number isn’t allowed because they’re different types. We must use the dereference operator to follow the reference to the value it’s pointing to.

    We can rewrite the code in Listing 15-6 to use a Box instead of a reference; the dereference operator used on the Box in Listing 15-7 functions in the same way as the dereference operator used on the reference in Listing 15-6:

    Filename: src/main.rs

    1. fn main() {
    2. let x = 5;
    3. assert_eq!(5, x);
    4. assert_eq!(5, *y);
    5. }

    Listing 15-7: Using the dereference operator on a Box

    The main difference between Listing 15-7 and Listing 15-6 is that here we set y to be an instance of a box pointing to a copied value of x rather than a reference pointing to the value of x. In the last assertion, we can use the dereference operator to follow the box’s pointer in the same way that we did when y was a reference. Next, we’ll explore what is special about Box that enables us to use the dereference operator by defining our own box type.

    Let’s build a smart pointer similar to the Box type provided by the standard library to experience how smart pointers behave differently from references by default. Then we’ll look at how to add the ability to use the dereference operator.

    The Box type is ultimately defined as a tuple struct with one element, so Listing 15-8 defines a MyBox type in the same way. We’ll also define a new function to match the new function defined on Box.

    Filename: src/main.rs

    We define a struct named MyBox and declare a generic parameter T, because we want our type to hold values of any type. The MyBox type is a tuple struct with one element of type T. The MyBox::new function takes one parameter of type T and returns a MyBox instance that holds the value passed in.

    Let’s try adding the main function in Listing 15-7 to Listing 15-8 and changing it to use the MyBox type we’ve defined instead of Box. The code in Listing 15-9 won’t compile because Rust doesn’t know how to dereference MyBox.

    Filename: src/main.rs

    1. impl<T> MyBox<T> {
    2. fn new(x: T) -> MyBox<T> {
    3. MyBox(x)
    4. }
    5. }
    6. fn main() {
    7. let x = 5;
    8. let y = MyBox::new(x);
    9. assert_eq!(5, x);
    10. assert_eq!(5, *y);
    11. }

    Listing 15-9: Attempting to use MyBox in the same way we used references and Box

    Here’s the resulting compilation error:

    1. $ cargo run
    2. Compiling deref-example v0.1.0 (file:///projects/deref-example)
    3. error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
    4. --> src/main.rs:14:19
    5. |
    6. 14 | assert_eq!(5, *y);
    7. | ^^
    8. For more information about this error, try `rustc --explain E0614`.
    9. error: could not compile `deref-example` due to previous error

    Our MyBox type can’t be dereferenced because we haven’t implemented that ability on our type. To enable dereferencing with the * operator, we implement the Deref trait.

    Treating a Type Like a Reference by Implementing the Deref Trait

    As discussed in the section of Chapter 10, to implement a trait, we need to provide implementations for the trait’s required methods. The Deref trait, provided by the standard library, requires us to implement one method named deref that borrows self and returns a reference to the inner data. Listing 15-10 contains an implementation of Deref to add to the definition of MyBox:

    Filename: src/main.rs

    Listing 15-10: Implementing Deref on MyBox

    The type Target = T; syntax defines an associated type for the Deref trait to use. Associated types are a slightly different way of declaring a generic parameter, but you don’t need to worry about them for now; we’ll cover them in more detail in Chapter 19.

    We fill in the body of the deref method with &self.0 so deref returns a reference to the value we want to access with the * operator; recall from the “Using Tuple Structs without Named Fields to Create Different Types” section of Chapter 5 that .0 accesses the first value in a tuple struct. The main function in Listing 15-9 that calls * on the MyBox value now compiles, and the assertions pass!

    Without the Deref trait, the compiler can only dereference & references. The deref method gives the compiler the ability to take a value of any type that implements Deref and call the deref method to get a & reference that it knows how to dereference.

    When we entered *y in Listing 15-9, behind the scenes Rust actually ran this code:

    1. *(y.deref())

    Rust substitutes the * operator with a call to the deref method and then a plain dereference so we don’t have to think about whether or not we need to call the deref method. This Rust feature lets us write code that functions identically whether we have a regular reference or a type that implements Deref.

    The reason the deref method returns a reference to a value, and that the plain dereference outside the parentheses in *(y.deref()) is still necessary, is to do with the ownership system. If the deref method returned the value directly instead of a reference to the value, the value would be moved out of self. We don’t want to take ownership of the inner value inside MyBox in this case or in most cases where we use the dereference operator.

    Note that the * operator is replaced with a call to the deref method and then a call to the * operator just once, each time we use a * in our code. Because the substitution of the * operator does not recurse infinitely, we end up with data of type i32, which matches the 5 in assert_eq! in Listing 15-9.

    Deref coercion was added to Rust so that programmers writing function and method calls don’t need to add as many explicit references and dereferences with & and *. The deref coercion feature also lets us write more code that can work for either references or smart pointers.

    To see deref coercion in action, let’s use the MyBox type we defined in Listing 15-8 as well as the implementation of that we added in Listing 15-10. Listing 15-11 shows the definition of a function that has a string slice parameter:

    Filename: src/main.rs

    1. fn hello(name: &str) {
    2. }
    3. fn main() {}

    Listing 15-11: A hello function that has the parameter name of type &str

    We can call the hello function with a string slice as an argument, such as hello("Rust"); for example. Deref coercion makes it possible to call hello with a reference to a value of type MyBox, as shown in Listing 15-12:

    Filename: src/main.rs

    Listing 15-12: Calling hello with a reference to a MyBox value, which works because of deref coercion

    Here we’re calling the hello function with the argument &m, which is a reference to a MyBox value. Because we implemented the Deref trait on MyBox in Listing 15-10, Rust can turn &MyBox into &String by calling deref. The standard library provides an implementation of Deref on String that returns a string slice, and this is in the API documentation for Deref. Rust calls deref again to turn the &String into &str, which matches the hello function’s definition.

    If Rust didn’t implement deref coercion, we would have to write the code in Listing 15-13 instead of the code in Listing 15-12 to call hello with a value of type &MyBox.

    Filename: src/main.rs

    1. use std::ops::Deref;
    2. impl<T> Deref for MyBox<T> {
    3. type Target = T;
    4. fn deref(&self) -> &T {
    5. &self.0
    6. }
    7. }
    8. struct MyBox<T>(T);
    9. impl<T> MyBox<T> {
    10. fn new(x: T) -> MyBox<T> {
    11. MyBox(x)
    12. }
    13. }
    14. fn hello(name: &str) {
    15. println!("Hello, {name}!");
    16. }
    17. fn main() {
    18. let m = MyBox::new(String::from("Rust"));
    19. hello(&(*m)[..]);
    20. }

    Listing 15-13: The code we would have to write if Rust didn’t have deref coercion

    The (*m) dereferences the MyBox into a String. Then the & and [..] take a string slice of the String that is equal to the whole string to match the signature of hello. This code without deref coercions is harder to read, write, and understand with all of these symbols involved. Deref coercion allows Rust to handle these conversions for us automatically.

    When the Deref trait is defined for the types involved, Rust will analyze the types and use Deref::deref as many times as necessary to get a reference to match the parameter’s type. The number of times that Deref::deref needs to be inserted is resolved at compile time, so there is no runtime penalty for taking advantage of deref coercion!

    Similar to how you use the Deref trait to override the * operator on immutable references, you can use the DerefMut trait to override the * operator on mutable references.

    Rust does deref coercion when it finds types and trait implementations in three cases:

    • From &T to &U when T: Deref
    • From &mut T to &mut U when T: DerefMut

    The first two cases are the same as each other except that the second implements mutability. The first case states that if you have a &T, and T implements Deref to some type , you can get a &U transparently. The second case states that the same deref coercion happens for mutable references.