[SOLVED] Why dereferencing is applied also to non-reference binding?


#1

Hi,

I cannot understand why this piece of code is compiling:

struct Foo {
    x: i32
}

impl std::ops::Deref for Foo {
    type Target = i32;

    fn deref(&self) -> &i32 {
        &self.x
    }
}

fn main() {
    let foo = Foo { x: 0 };
    foo.count_zeros();
}

I could understand it if count_zeros took a &self, so Rust has to dereference it before invoking the method, but in this case it takes a self and the binding foo is not a reference, so why it is working?

Actually I came to this question since I was trying to dig into Iterator traits, and I’ve noticed that Vec<T> doesn’t implement directly Iterator<T>, but indirectly via Deref<Target=[T]>.


#2

This is discussed in the book here: https://doc.rust-lang.org/stable/book/deref-coercions.html#deref-and-method-calls


#3

Thank you for the answer.

I’ve already read this part of the book, but I think I missing something.
It’s written that:

If you have a type U, and it implements Deref<Target=T>, values of &U will automatically coerce to a &T

So since in my example there are no references this rule shouldn’t be applied, right?

The other rule is about method call and it basically says (correct if I am wrong) that Rust will apply as many * as needed in order to match the method signature (for the self parameter). In my example no * are needed since the method signature matches already the method call. I am calling the method passing self and the method takes a self.

So of course I am missing something :confused:

Thank you.


#4

The method call syntax doesn’t only do auto-derefs but also auto-refs, ie. inserting & or &mut when necessary. See the definite Stackoverflow answer to auto-dereferencing: http://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules


#5

So, this mean that in my example Rust automatically put *& before my foo binding? So something like this:

(*(&(foo)).count_zeros();

Ok, now it is more clear.
Anyway in my opinion, maybe in the new version of the book, it should explained better which is the algorithm used by Rust to match method call. I will check it and in case try to suggest a deeper explanation.

Thank you all!


#6

Exactly. You can verify this by looking at the (elaborate) MIR code in your Playground link. Slightly simplified extract:

    var0 = Foo { x: const 0i32 };    // scope 0 at <anon>:14:15: 14:27
    tmp3 = &var0;                    // scope 1 at <anon>:15:5: 15:8
    tmp2 = <Foo as std::ops::Deref>::deref(tmp3); // scope 1 at <anon>:15:5: 15:8
    tmp1 = (*tmp2);                  // scope 1 at <anon>:15:5: 15:8
    tmp0 = core::num::<impl i32>::count_zeros(tmp1); // scope 1 at <anon>:15:5: 15:22