I'm pretty sure that the * operator is special-cased for references inside the compiler to bypass the normal Deref desugaring— This impl is only used in generic contexts when a reference is passed as a T:Deref type parameter.
The reason is the same as with implementation of Add for i32. If Add::add implemented as fn add(self, other: i32) -> i32 { self + other } doesn't surprise you then why does implementation of deref for references surprises you?
To add to previous replies: Unlike for Add, where the fact that + is magically overloaded for built-in primitive types, and Add implementations are implemented in terms of + itself, for Deref and &T, the fact that (prefix) * operator for &T is compiler built-in, and not desugared in terms of Deref is more than just an arbitrary implementation detail of the compiler and standard library. It’s in fact necessary, as otherwise, we would need infinite desugaring.
See, the desugaring of *x for some value x: Foo of a type Foo: Deref<Target = Bar> (and in a context where *x is only accessed immutably, otherwise DerefMut would be involved) goes as follows:
*x
becomes
*Deref::deref(&x)
as you can see, the desugared code involves a call to Deref::deref, and a reference expression (the prefix & operator applied to x), but also the desugared code contains a new call to the deref operator (prefix) *itself!
Let’s look at the types…
Expression
Type
x
:
Foo
*x
:
Bar
x
:
Foo
&x
:
&Foo
Deref::deref
:
impl Fn(&Foo) -> &Bar
Deref::deref(&x)
:
&Bar
*Deref::deref(&x)
:
Bar
as you can see, the additional steps of taking a reference to x and dereferencing the result do fit the types perfectly. We need to create a reference to pass to Deref::deref, as &self method, and also we need to dereference the reference it returns, in order to get the type of *x matching correctly. Notably the dereferencing operation in this desugaring dereferences an ordinary immutable reference, in this case of type &Bar. This is why this type’s deref operator must be special. Every desugaring of (prefix) * contains a new instance of (prefix) * but this time no longer for custom types, but for the concrete type &T (for some T) or for &mut T in case of a mutably accessed *x desugaring to *DerefMut::deref_mut(&mut x) which is why &T and &mut T have compiler built-in implementations of (prefix) * operator, otherwise you’d keep desugaring/expanding indefinitely
My claim of necessity was only about deref * operator, and I meant to explicitly contrast with + where I claim that it’s not necessary but just an “arbitrary implementation detail of the compiler and standard library”.
The conclusion of necessity of course also only holds if the desugaring, namely
*x --> *Deref::deref(&x)
isn’t altered. Of course, with a different desugaring like
one could avoid the need to make * skip the Deref trait for &T.
Except of course that deref * operator for &T has additional capabilities not achievable with Deref::deref, such as support for partial borrowing, so the’re alternative arguments for a necessarily compiler built-in implementation, anyways.
Edit: Trying to reproduce a demo example to show off additional capabilities of built-in dereferencing of &T, I’m realizing that this argument might possibly only apply to &mut T and Box<T>. For &T perhaps, there’s only the argument of analogy that when &mut T needs special casing, it isn’t worth to pursue the alternative desugaring and avoiding the special-case for &T exclusively.
It would be possible, but it's not about separating deref coercion from the * operator, it is about distinguishing "primitive deref" from "user deref". The complication is that for x: &T, <&T as Deref>::deref is fn(&&T) -> &T, but *x converts a value of type &T to a place of type T, which is a concept not expressible by functions.
Also, deref for references (and pointers) is special in that it's part of a "pure place expression", whereas user deref isn't. This has impact on borrow splitting (possible behind a builtin deref but not a user deref) as well as runtime data validity requirements.
But in fairness, LCCC at least claims to support a library implementation of Box<T> with equivalent capabilities as rustc's partially built-in Box<T>, so it is possible capture all of the magic in (new) magic trait impls.
The most likely alternative to an apparently recursive trait implementation is no library implementation at all, such as in C++ where pointers don't have an operator-> method and you need to use a shim template for pointers or classes with operator-> instead.
@tczajka Using a compiler builtin seems digestible thought and implementation. But why is there no builtin tag used for Deref on &T ? Instead it uses *self which am not able to understand.
In impl<T> Deref<T> for &T, Self (the type) is &T, so self (the value created from &self in the parameters) is &&T, and doing *self does a copy to give you &T.
When you write *x, one of a few things happens:
If x is a reference to a Copy type, the value is copied (this is what happens inside <&T>::deref).
If x is a reference, and this is a place expression that gets rereferenced, like in let y = &*x, then a reborrow happens.
If x is not a reference but implements Deref, then a call to Deref::deref is inserted.
This is super simplified, but the point is that only sometimes does * become a Deref::deref call. The * inside <&T>::deref doesn't ever become another Deref::deref since it's a copy. Not sure how the one added in front when expanding to *Deref::deref(&x) works, but I'm guessing that one is a special operator that's only allowed to copy or reborrow.
But then how do you use a reference where a generic type parameter constrained by a Deref bound is expected? Not being able to do that would likely be unacceptable by any reasonable measure.
*box is special as Box is a lang item.
for example let foo = *box; will move out of the box, and won't return a reference to the box, so *box doesn't call the Deref operator, you can hook with gdb and see what exactly is called and when