Is deref coercion applied for &&T to &T

fn test(m: &MyStruct) take &MyStruct type, but rr is &&MyStruct, it compiled successfully.


use std::ops::Deref;

struct MyStruct;

fn test(m: &MyStruct) {}

fn main() {
    let ms = MyStruct {};
    let rr = &&ms;

    test(rr);
}

The question is:

  • Does rust compiler apply deref coercion ?
  • Does rust apply following Deref trait implementation? If it does, I debug this code, set a breakpoint on implementation, but never be trigged to stop.
#[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
    }
}

Yes, at least in principle one would usually classify this as deref-coercion. The rust reference does so (there’s no other type of coercion that applies).

However dereferencing build-in types like Box<T> or &T or &mut T is special in that it doesn’t actually call the Deref[Mut] trait infrastructure. Instead the implementation in fn deref uses the dereferencing operator (as you can see, its function body is just *self), and similar to *-operator the deref-coercion, too, does not trigger an actual call to Deref::deref (or to DerefMut::deref_mut).

The situation is similar with indexing slices and arrays or adding integers. All of these operations are implemented by the compiler directly, not actually calling into the trait infrastructure.

On the other hand, this is for the most part an implementation detail. E.g. you wouldn’t usually care if 1 + 1 does call the Add::add method for i32 is whether it’s compiler-internal.

Small aside: AFAIK, in generic code, you can observe a call happening.

I.e. if you do

use std::ops::Deref;

struct MyStruct;

fn test<T>(m: &T) {}

fn generic<D, T>(rr: &D)
where
    D: Deref<Target = T>,
{
    test::<T>(rr);
}

fn main() {
    let ms = MyStruct {};
    let rr = &&ms;
    generic::<_, MyStruct>(rr);
}

then it should actually call into the Deref trait.

For dereferencing &&T to &T, there’s another important point:

The “proper” deref coercion involving Deref::deref would convert &'a &'b T into &'a T, while actually rustc is nive enough to support &'a &'b T to &'b T conversion through that coercion:

fn demonstration<'a, 'b, T>(x: &'a &'b T) -> &'b T { x }
use std::ops::Deref;
fn this_does_not_work<'a, 'b, T>(x: &'a &'b T) -> &'b T { x.deref() }
// ^^^ does not compile
// only works when limiting the return lifetime to `'a`   vvvvv
fn works_but_limited_lifetime<'a, 'b, T>(x: &'a &'b T) -> &'a T { x.deref() }

I guess this can be interpreted as a point in favor of not considering this to be ordinary deref coercion but instead something special. It’s not entirely special though, as it’s not limited to &&T to &T – e.g.

fn demonstration2<'a, 'b, T>(x: &'a mut &'b T) -> &'b T { x }

or

fn demonstration3<'a, 'b, T>(x: &'a mut Arc<&'b T>) -> &'b T { x }

works as well.

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.