I feel a few nontrivial parts of the language need to be understood to understand how Deref
and the derefence operator (prefix *
) work in Rust:
- How
*i
notionally desugars into the Deref
operator
- And what place expressions are
- The fact that
*
is built-in for some types and doesn't use the Deref
trait
Running example
I'll use this (and some built-in types) as a basis for the examples.
struct MyFieldOne;
struct MyFieldTwo;
struct MyStruct {
a: MyFieldOne,
b: MyFieldTwo,
}
// You generally don't want to do this unless the fields is the "only" thing
// you contain in some sense, but for the sake of illustration...
impl Deref for MyStruct {
type Target = MyFieldOne;
fn deref(&self) -> &Self::Target {
&self.a
}
}
// In contrast this is fine for any field... and for `Self`
impl AsRef<MyFieldTwo> for MyStruct {
fn as_ref(&self) -> &MyFieldTwo {
&self.b
}
}
How *my_struct
notionally desugars
The signatures of deref
and as_ref
look the same -- they look like types of shared reference conversion. And that basically is the pont of AsRef
; it's the Into
of shared references:
fn example(my_struct: MyStruct) {
// This compiles
let b = my_struct.as_ref();
// You can uncomment this to confirm `b` is a `&MyFieldTwo`
// let _: () = b;
}
// Side note: You would need an annotation for `b` if there is more than
// one `AsRef<_>` implementation for `MyStruct` due to ambiguity
And if you call deref
, which is just a trait method, it works the same way:
fn example(my_struct: MyStruct) {
// This compiles
let a = my_struct.deref();
// You can uncomment this to confirm `a` is a `&MyFieldOne`
// let _: () = a;
}
// Side note: You can only implement `Deref` once so there can't be
// an ambiguity like there can be with `AsRef<_>`
However, this isn't the case for the dereference operator, *
. When *
corresponds to a Deref
implementation, it looks like
*my_struct // notionally becomes
*<MyStruct as Deref>::deref(&my_struct)
// ^
Note how there's still a *
on the front! So when we apply *
to my_struct
, the type of the expression is going to be MyFieldOne
, and not &MyFieldOne
. So here:
fn example(my_struct: MyStruct) {
let a = *my_struct;
}
We actually get an error:
error[E0507]: cannot move out of dereference of `MyStruct`
--> src/lib.rs:4:13
|
4 | let a = *my_struct;
| ^^^^^^^^^^ move occurs because value has type `MyFieldOne`, which does not implement the `Copy` trait
Because MyFieldOne
has to be moved, not copied, and you can't move through a dereference similar to how you can't move from underneath a reference.
That would be quite limiting for the *
operator if it weren't for...
Place expressions
We can change the last example to compile by doing this instead:
fn example(my_struct: MyStruct) {
// let a = *my_struct; // cannot move out of dereference of `MyStruct`
let a = &*my_struct;
// You can uncomment this to confirm `a` is a `&MyFieldOne`
// let _: () = a;
}
Just by immediately taking a reference again, we've prevented the move, and now the example acts like my_struct.deref()
did. Why did it prevent the move?
Rust has place and value expressions, and place and value contexts. Place expressions represent some actual memory location; in the example, *my_struct
represents the memory location of the MyFieldOne
field. With let a = *my_struct
, this place expression was bound to a
, which results in moving from the memory to the bound variable. Or rather it would if you were allowed to move out of that location.
But when we write &*my_struct
, the *my_struct
place expression is now in a place context, and no move occurs.
This also related to why Deref::deref
looks like a reference conversion method: we can't write a method that looks like this:
impl DirectDeref for MyStruct {
type Target = MyFieldOne;
fn direct_deref(&self) -> Self::Target {
self.a
// error[E0507]: cannot move out of `self.a` which is behind a shared reference
}
}
So instead we return a reference... and that's also why the notional desugaring puts a *
on the results of the method call! That way one layer of indirection actually is removed.
But doesn't desugaring to yourself seem like it might cause problems...?
Built-in *
operations
Now let's try to walk through a dereference of an &i32
using the notional desugaring from above. Here's the implementation signature that @jofas linked to in non-generic form:
impl<'a> Deref for &'a i32 {
type Target = i32;
fn deref(self: &&'a i32) -> &i32 { /* ... */ }
}
And looking at a dereference:
fn hmm(ri: &i32) {
// vv This is an `&i32`
let i = *ri;
let i = *<&i32 as Deref>::deref(&ri);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is also an `&i32`...
// Apply the notional desugarring again?!??
let i = *
<&i32 as Deref>::deref( <&i32 as Deref>::deref(&ri) )
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Still an `&i32`...
}
That's clearly going nowhere. We could also figure out things can't work this way if we had written out the body of deref
:
impl<'a> Deref for &'a i32 {
type Target = i32;
fn deref(self: &&'a i32) -> &i32 {
*self
}
}
If that *self
called Deref::deref
, it would be infinite recursion.
So when applied to references, *
is not the notional desugaring, it's a built-in operation.
Why have the blanket implementation then? For generics! If you want to use *
on a generic variable T
, which may or may not be a reference, you need to require that ability with a trait bound (T: Deref<Target = ...>
).
Addendum