How does the compiler distinguish the method call when multiple candidates exist?

Right - one layer of autoref or an arbitrary number of deref coercion, and possibly one slice coercion.

1 Like

In general, from T to &T is a compiler action. We do not have the operation trait to control this like * did(i.e. * can be overloaded by Deref trait).

That is to say, the actual receiver type whose behind type is the same as that of the receiver at most has an extra & or & mut than T.

No.

Edit: example for case coercion to U

why not? Consider this case

struct Foo;
impl Foo{
   fn show(self:&&Foo){}
}
fn main(){
   let foo = Foo;
   foo.show();
}
T  // assume it's the provenance type
&T  // add by compiler
& mut T  // add by compiler

In this case, T is Foo, no type in the list would match &&Foo

You missed the * and coercion unsized-coercions cases.

Anyway, * operator cannot increase the & for a type T, I think.

struct Foo;
impl Deref for Foo{
   type Target = &Foo; // how this lifetime would be represented
   fn deref(&self)->&Self::Target{
      todo!()
   }
}

:sweat_smile: These are not conflicting. I think @xmh0511 is just saying you wouldn't get more than one reference level, not saying there's no other possiblities.

2 Likes

Yes, the reference operators cannot be overloaded and method resolution adds at most one. There's no cap to how many it could take (you can always take another reference), but nested reference receivers are rare and there has to be some limit so that searches terminate.

Autoderef on the other hand will always eventually find a type that doesn't deref, or in degenerate cases, loop. So there technically needs be no limit (but in practice there is one so that the non-degenerate case doesn't pay the compile-time cost of loop detection).

2 Likes

You got it.:grinning:

@quinedot Re-read Method call expressions - The Rust Reference carefully, I found a confusing point

Then, for each candidate type T, search for a visible method with a receiver of that type in the following places:

  • T's inherent methods (methods implemented directly on T).
  • Any of the methods provided by a visible trait implemented by T.

Consider this example

trait MyTrait{
   fn show(&self);
}
impl MyTrait for i32{
   fn show(&self){}
}
fn main(){
  let i:&i32 = &0;
  i.show();
}

The candidate types are

&i32 
&&i32
& mut &i32
i32
&i32
& mut i32

In this example,

for each candidate type T, the search for a visible method with a receiver of that type,

for candidate type T that is &i32, <i32 as MyTrait>::show will be looked up, however, the trait is implemented by i32 not T(that is &i32).

I suppose the intent of the second point should be:

Any of the methods provided by a visible trait implemented by T whose receiver is of type T

Moreover, in order to make inherent method have a higher precedence, we may want to say

T's inherent methods (methods implemented directly on T), if found nothing, then

Is my understanding right?

Right. I think you're not missing the point.

But if you read carefully, the full context is clear:

Then, for each candidate type T, search for a visible method with a receiver of that type in the following places:

  1. T's inherent methods (methods implemented directly on T).
  2. Any of the methods provided by a visible trait implemented by T. If T is a type parameter, methods provided by trait bounds on >T are looked up first. Then all remaining methods in scope are looked up.

Note: the lookup is done for each type in order, which can occasionally lead to surprising results.

The bold texts a visible method with a receiver of that type and in order already give the exact same meaning.

And I'd be surprised you accept (haven't argued about) the phrase in the first condition, T's inherent methods, since there is no direct implementation on &i32 from your point of view. [1]

My suggestion is as for any method, stick to the receiver type instead of the implementing type.


Update:

add an example link to show the method call order when no direct implementation on the receiver type: Rust Playground


  1. see the update for what I mean ↩ī¸Ž

1 Like

Yeah, I think it's really "any inherent method". With this example, the trait can never win [1] due to the presence of an inherent method with the same receiver:

impl MyTrait for &A{
   fn show(self){
       println!("from MyTrait for A")
   }
}

impl A {
    fn show(&self) {
       println!("from inherent method of A")
   }
}

When there's different levels of reference involved

-    fn show(&self) {
+    fn show(self: &&Self) {

There can be different winners.


  1. outside of generic context where you don't know the type is A, modulo visibility of the method, etc ↩ī¸Ž

2 Likes

My suggestion is as for any method, stick to the receiver type instead of the implementing type.

Yes, I think so. I oversight the first bullet. I also think the lookup should stop once the candidate functions set is found for a candidate type.

So, I think the rust reference wording should be updated in three aspects:

  1. Once the inherent methods are found, the lookup will stop
  2. In both bullets, we just need to say search these methods whose receiver types are identical to the candidate type we are finding for. Instead of saying the method in the implementation for the candidate type.
  3. Once the lookup finds a non-empty set of candidate functions for a candidate type, the lookup will stop searching for the remaining candidate types.

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.