Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Method Call Resolution #1018

Open
eeff opened this issue May 6, 2021 · 5 comments
Open

Method Call Resolution #1018

eeff opened this issue May 6, 2021 · 5 comments

Comments

@eeff
Copy link

eeff commented May 6, 2021

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
Copy link
Contributor

ehuss commented May 7, 2021

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 rust-lang/rust#26007 (comment)

@eeff
Copy link
Author

eeff commented May 13, 2021

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

@eeff
Copy link
Author

eeff commented Aug 18, 2021

@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
Copy link
Contributor

ehuss commented Aug 18, 2021

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
Copy link

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();
}

adetaylor added a commit to adetaylor/reference that referenced this issue Jan 30, 2025
This section of the reference has been oversimplistic for some time (rust-lang#1018
and rust-lang#1534) and various rewrites have been attempted (e.g. rust-lang#1394, rust-lang#1432).
Here's another attempt!

My approach here is:
* Stop trying to keep it short and concise
* Document what actually happens in the code, step by step

This does result in a long explanation, because we're trying to document
nearly 2400 lines of code in `probe.rs`, but doing otherwise feels
as though we'll continue to run into criticisms of oversimplification.

This rewrite documents the post-arbitrary-self-types v2 situation,
i.e. it assumes rust-lang/rust#135881 has
landed. We should not merge this until or unless that lands.

This PR was inspired by discussion in rust-lang#1699. If we go ahead with this
approach, rust-lang#1699 becomes irrelevant. There was also discussion at
rust-lang/cargo#15117 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants