Why does Rust's standard library require two levels of dereferencing for self?

impl<T: ?Sized> Borrow<T> for &T {
fn borrow(&self) -> &T {
&**self
}
}

Why does the code above require two levels of dereferencing? This is not only seen in this trait but also in others like Drop. I don't quite understand why we need to dereference twice when we only reference once.

It's similar to the following code:

rust

Copy

let a = 3;
let p = &a;

And then, in order to obtain a reference to the number 3, we have to write:

rust

Copy

let p1 = &**p;

Isn't it funny? Shouldn't it be let p1 = &*p;?

So, I don't understand why sometimes it requires two dereferences. It even affects my understanding of pointers.

There is no code above.

Yes, it should.

error[E0614]: type `{integer}` cannot be dereferenced
 --> src/main.rs:4:15
  |
4 |     let p1 = &**p;
  |               ^^^

You'll need to be more precise about what you're asking in order to get the answer you're looking for to clear things up.

I have refresh.

The implementation is for &T therefore &self == &&T and you must dereference twice to get a T.

5 Likes

Another interesting angle to look from is the question of “why are there so many steps”. The example in question of turning &&T into &T should – if you think about the hardware level – really only take one step, shouldn’t it? Why is it 3 steps, removing an indirection twice, then adding back one?

There are multiple answers to multiple aspect of these considerations.

First off, as people who care about performance, does the hardware do some unnecessary extra steps here? No! (Not even without optimizations.) The reason is that Rust at a language level has the concept of “places” in addition to “values”. If you have a pointer value like p: &mut i32, then an expression like *p on its own doesn’t do anything yet at run-time, on its own. It simply denotes the “place” that the pointer points to. If you then read from this place, only then would you get something like a mov instruction with a pointer argument reading from RAM[1]. So something like let x = *p; actually does a pointer dereferencing at run-time. However if you just take a pointer to the place again, that’s no longer the case. Writing let q = &*p;, which turns your &mut i32 into a &i32, this combination of dereferencing and borrowing corresponds to no operation at all at run-time.

The Borrow<T> for &T implementation above thus has &* in the front doing essentially nothing (at run-time), and only the inner/right * operation corresponds to a read from memory (because it’s a place that is actually used by-value).

Second, as people who care about ergonomics, this &**self expression business seems a bit tedious to write, doesn’t it? If it’s just a single dereferencing, why can’t we just write *self? Answer: we can! Follow-up question: Why doesn’t the standard library do that? Answer: I can only speculate, but it might be for being explicit and consistent.

Let's start with consistency: The thing that this Borrow implementation is consistent with is the corresponding Borrow implementation for &mut T (and with the similar BorrowMut implementation of &mut T). This one couldn’t just use *self because that would have type &mut T, not the expected &T, oh wait it can, but something else is going on here… implicit coercions and/or reborrows, leading us to expliciness in the next paragraph. The implementation of Borrow for &T does work by just writing *self to turn &&T into &T because reading from the &T place that *self denotes by-value can just copy (via Copy trait) the reference; the same is not true e.g. for &mut &mut T to &mut T conversion (used for &mut T: BorrowMut<T>).

Next up, explicitness. Rust language design acknowledges that often you don’t really care about the exact number of indirections, and don’t want to explicitly play the game of finding the correct operator soup for the correct type conversion. After all, conversions from &&T to &T or &&mut T to &T or &mut &mut T to &mut T, etc.. have only one reasonable implementation anyways. In any case, Rust can do these conversions implicitly “called ‘deref coercion’”, so all these implementations could indeed just write self and it’d work! However, for some reason, the standard library authors seem to have preferred the explicit approach here, which is why you’ll find &**self (or &mut **self) in these trait implementations.


  1. in a naive translation of code to machine code, i.e. ignoring any optimizations ↩︎

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.