On the surface, Deref is merely the trait that is invoked when the “dereference operator”, the sometimes unwelcome and intimidating a unary prefixed asterisk (*T), is invoked. That is, is describes to Rust how to take a reference as an argument and return the value. It’s designed to facilitate creating smart pointers, such as reference counting (Rc and Arc are smart pointers built by the standard library) and other types that manage a chunk of data, but want to facilitate access to that data, such as Vec<T> and String.

Banner: Deref trait; what it is and what it does

In some sense, though Deref provides more power than this. To start with, it’s invoked more frequently than you might expect. Rust provides “auto-deref” behavior for method calls, which means that a) there are fewer asterisks in your code made than you might expect if you’re from a language where they’re all explicit and b) until you’re proficient in Rust, it’s sometimes unclear what’s actually happening.

Counter-intuitively, the Deref trait requires implementers to return a reference. That is, it provides a mutable borrow. This avoids unnecessary movement and cloning, but definitely incurs some mental overhead. 

To make things more complicated, library authors are allowed to use Deref to do whatever they like. This includes attempting to wedge dynamic sub-typing into the language. This so-called “deref polymorphism” is a known anti-pattern.

What is "dereferencing"?

Dereferencing means accessing the data that’s referred to by a reference. Let’s work through an example, so that it’s easy to see what I mean.

Let’s say we have a variable a, that is holding a value 123:

				
					let a = 123;
				
			

We can now take a reference, also called borrowing in Rust terminology, to a and store it as the variable b. The ampersand (&) symbol is used to do that:

				
					let b = &a;
				
			

Now that we have a reference, we need to dereference it to get the value 123 again. To dereference a reference, add the asterisk (* ) symbol to the left of it.

				
					let c = *b;
				
			

To use a very stretched analogy, if buried treasure is the value, the reference might be a treasure map.

A fantasy map with overlay text, "Deref allows you to access the treasure from the treasure map."
A reference is like a treasure map. Dereferencing returns the treasure. Built with Perilous Shores (https://watabou.github.io/perilous-shores)

Are references the same thing as borrows?

Almost, but not quite. In most cases, the terms borrows and references can be used interchangeably.

Borrowing has a slightly stronger meaning however. It’s tied to Rust’s ownership system. All borrows are references, but a reference is not necessarily a borrow.

Borrowing is a Rust-specific concept, whereas references are a term that’s used in other language communities.

To understand the difference, I recommend watching this lecture I gave entitled “Boxes, Heaps and Stacks”  on 1.5x speed:

The simple case

Generally speaking, the dereference operator works by taking a reference and returning what’s being referred to (known formally as the referent).

In the following snippet, a and c are both bound to 123, whereas b is a reference to the copy of the number bound to a. Because of Rust’s use of copy semantics for types which implement the Copy trait, a  and c each have their own copy of 123.

				
					fn main() {
    let a = 123;
    let b = &a;
    let c = *b;
    
    assert_eq!(a, c);
}

				
			

Adding custom semantics

The Deref trait allows implementations to define their own semantics, however. There’s no requirement for the type that’s provided by dereferencing to be the type that you started with.

The trait’s definition requires implementers to specify an associated type Target. Let’s see how that works:

In the example below, the Curse<T> is a tuple struct that ignores the original value. During the dereference step, it returns a reference to 42, irrespective  of whatever was provided. I think of operator overloading as a sharp knife feature. You’re allowed to do whatever you want, but you risk cutting yourself.

				
					use std::ops::Deref;

struct Curse<T>(T);

impl<T> Deref for Curse<T> {
    type Target = usize;
    
    fn deref(&self) -> &<Self as Deref>::Target {
        &42 // wat
    }
   
}

fn main() {
    let a = 123;
    let b = Curse(a);
    let c = *b;
    
    println!("{a} {c}");
}

				
			

Essential for smart pointers

Deref enables the definition of, and perhaps more importantly, greatly eases the use of (smart) pointer types in Rust.

The term “pointer type” can be somewhat off-putting. By this, I mean any type which manages some backing data structure. Without that backing data, the type itself would be somewhat useless. This includes types that you may not think of as pointer types, such as String and Vec<T>.

Pointer types are essentially a gateway or interface to some inner type. The Deref trait is the hook that library authors can use to provide the desired functionality.

The term “smart pointer” means a pointer type with added semantics, such as reference counting. The canonical examples in the standard library are std::rc::Rc and std::sync::Arc, which both provide shared ownership via reference counting.

The Deref trait makes references easier to use

Rust code uses lots of references. That’s because, when you call functions by value in Rust, you’re saying goodbye to that value. Functions that take a arguments by value—that is when the value “moves” into the function—take on responsibility for cleaning up those values.

Functions also pop up more frequently than they might seem. Consider testing whether two values are the same:

				
					a == b
				
			

To compare a and b, Rust takes references to both of them. To perform the equality comparison, a == b gets converted to the compiler to a.eq(&b). During the call to eq(), a is borrowed as &self.

Moreover, the dot operator — the one you use when you call a method — implicitly dereferences self.

The alternative to implicit dereferencing would be to require programmers to do it all manually. This would add a lot of clutter to the source code.

Deref can be dangerous

Relying too much on Deref, particularly by using it outside of its intended use case of facilitating the creation of smart pointers, can cause a lot of confusion for people using your code. To start with, error messages might suddenly seem to refer to types that are not present in a program.

While it’s handy that types automatically gain the ability to call functions that they dereference into, saving the irritation of manually re-implementing a lot of boilerplate when creating new (sub-)types, following that heading too far leads to deep water.

Acknowledgements

Thanks to Predrag Gruevski for your suggestions.

Further Reading

About the author

Tim McNamara, often known online as timClicks, is the author of Rust in Action and the founder of Accelerant. He offers Rust coaching and mentoring to Patreon supporters others directly via 1:1 support. Email tim@accelerant.dev to learn more.