rust-lang / reference

The Rust Reference
https://doc.rust-lang.org/nightly/reference/
Apache License 2.0
1.25k stars 488 forks source link

Method Call Resolution #1018

Open eeff opened 3 years ago

eeff commented 3 years ago

After reading the Method Call Expression section, I am still confused about the method resolution. For example, the following code outputs in inherent impl instead of in trait impl. But according to the reference, shouldn't the type candidate list be &Foo, &&Foo, &mut &Foo, Foo, &Foo, &mut Foo? And so the trait method would be found first? Could somebody shed more light on this ?

trait Trait {
    fn method(self);
}

struct Foo;

impl Foo {
    fn method(&self) {
        println!("in inherent impl");
    }
}

impl Trait for &Foo {
    fn method(self) {
        println!("in trait impl");
    }
}

fn main() {
    let foo = &Foo;
    foo.method();
}

The output is:

$ cargo run
in inherent impl
$ rustc --version
rustc 1.53.0-nightly (f82664191 2021-03-21)

Found a related StackOverflow, but I still don't understand why this is so.

ehuss commented 3 years ago

Yea, the method call documentation could use some improvement. Niko recently mentioned this at https://rust-lang.zulipchat.com/#narrow/stream/237824-t-lang.2Fdoc/topic/documentation.20for.20method.20dispatch.

I think in your example, the key part is the type of the receiver. In the example, the method receiver is &Foo for both the inherent impl and the trait impl. Since inherent impls always take precedence over trait impls, the inherent impl is called.

To explain it in more detail, algorithm is more like:

  1. Create a list of "steps" by derefrencing. In this case, the steps are &Foo and Foo.
  2. Create two lists: the inherent and extension (trait) candidates by iterating over the steps and finding matching methods.
  3. For each step, search (first match wins):
    1. Step T, first inherent, then extensions
    2. Step &T, first inherent, then extensions
    3. Step &mut T, first inherent, then extensions
    4. For mut T, search const T methods, first inherent, then extensions

In this case, the search finds the inherent impl during the first step in 3.1, since it finds an &Foo receiver in the inherent impl.

There's a little more information in the rustc-dev-guide: https://rustc-dev-guide.rust-lang.org/method-lookup.html#method-lookup And the actual code is here: rustc_typeck/src/check/method/probe.rs

(Note: I am no expert on this, this is just my understanding of how it works, this isn't any sort of authoritative explanation.)

See also https://github.com/rust-lang/rust/issues/26007#issuecomment-270484928

eeff commented 3 years ago

@ehuss thanks for the explanation. I also posed a question in the user forum

eeff commented 3 years ago

@ehuss I think I misunderstand the concept inherent impl. As you said

In this case, the search finds the inherent impl during the first step in 3.1, since it finds an &Foo receiver in the inherent impl.

But isn't the inherent impl is for type Foo, not &Foo?

ehuss commented 3 years ago

It's not when the type the impl is declared for. It is the type of the receiver, which in this case is &Foo.

QuineDot commented 1 year ago

To reiterate the key part of #1321 over here, it is currently worded that

Then, for each candidate type T, search [...]

  • T's inherent methods [...]
  • visible trait[s] implemented by T

And the factual error is that it's not just the candidate's implementations that are considered.

I believe it's any visible implementation where candidate T is a valid receiver. Also, inherent implementations can conflict with each other. This is especially true under #![feature(arbitrary_self_types)], but is already true on stable today:

impl S {
    fn get_ref(self: Pin<&Self>) {}
}

fn error_e0034_multiple_applicable_items_in_scope(p: Pin<&S>) {
    p.get_ref();
}