Deref on &T does not result in infinite recursion?

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Deref for &T {
    type Target = T;

    #[rustc_diagnostic_item = "noop_method_deref"]
    fn deref(&self) -> &T {
        *self
    }
}

The above is obtained by referring the rust docs. Does the above not result in infinite deref calls ? if not why ?

2 Likes

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.

7 Likes

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?

2 Likes

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! :wink:

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

                                          *x
                            *Deref::deref(&x)
              *Deref::deref(&Deref::deref(&x))
*Deref::deref(&Deref::deref(&Deref::deref(&x)))
                         â‹®
                         â‹®
13 Likes

Unhelpful. OP didn't even mention Add or that it isn't surprising.

I certainly find it surprising.

I don't think the special case is really necessary.

For + and Add, it would be possible to have + always mean Add::add, and then you would simply define:

impl Add for i32 {
    type Output = i32;
    fn add(self, rhs: i32) -> i32 {
       builtin_add_i32(self, rhs)
    }
}

For * and Deref it's a bit more complicated but also doable in a similar fashion. One would have to separate "Deref coercion" from "the * operator".

2 Likes

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

*x --> magic_builtin_deref_for_immutable_references!(Deref::deref(&x))

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.

1 Like

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.

5 Likes

@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.

1 Like

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.

1 Like

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.

Special types usually have behavior built into the compiler. Here is Box’s Deref:

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized, A: Allocator> Deref for Box<T, A> {
    type Target = T;

    fn deref(&self) -> &T {
        &**self
    }
}

*self should get us a Box<T>, which we then dereference again… how do we dereference a Box<T>? Oh…

4 Likes

*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

2 Likes