Following the call-expression search process for a simple example

I will try to follow this procedure for finding a method detailed in the reference:

Minimal Example

struct OwnType;
impl OwnType { 
    fn a_method(&self){
      println!("Hi.");
    }
}

fn main(){
  let a = OwnType;
  let b = &a;
  b.a_method(); // prints Hi.
}
Procedure 1
  1. Initial Type of receiver (defined here): &OwnType
  2. Candidate list: &OwnType, &&OwnType, &mut &OwnType, OwnType, &OwnType (again), &mut OwnType,...
  3. Which one is the first having the method? Process answer: OwnType.
  4. The receiver is coerced &OwnType -> OwnType.
  5. OwnType expects &self which is &OwnType its type was coerced in the previous step.
  6. How isn't this a contradiction?
  7. Possible solution: now insert a & to OwnType to match the &Self. So the "coerced / dereferenced receiver" is now referenced.
Alternative Procedure
  1. Assign a self somewhere to &OwnType.
    • Now 1,2,3,4 (inclusive) are same as above.
  2. Initial Type of receiver (defined here): &OwnType
  3. Candidate list: &OwnType, &&OwnType, &mut &OwnType, OwnType, &OwnType (again), &mut OwnType,...
  4. Which one is the first having the method? Process answer: OwnType.
  5. The receiver is coerced &OwnType -> OwnType.
  6. Now the OwnType expects &self which is &OwnType which is what we have stored.
  7. This succeeds.

However, it's hard to run the process in many cases; I wonder what others think, and what conclusions they get.

@anon65535354 I think you will find most of us reading/replying here are humans and just let the compiler do its thing. It removes having a "hard" problem.

1 Like

Separately, I've written multiple experiments to try to show the seeming success of the first procedure:

struct OwnType;
impl OwnType { 
    fn takes_ref(&self){
      println!("You are shared or unique.");
    }
    fn takes_unique(&mut self){
      println!("You are unique.");
    }
}


fn main(){

  // case: unique reference.
  let mut a = OwnType;
  let mutable_ref = &mut a;
  mutable_ref.takes_ref(); // with first method
  // Finds OwnType::takes_ref(&self); it coerces the stored-apart &mut OwnType to &OwnType
  
  mutable_ref.takes_unique(); // with second method
  
  // case: shared reference
  let b = OwnType; 
  let shared_ref = &b;
  shared_ref.takes_ref();
  // self=OwnType, and puts the &OwnType we had in-there.
  
  // b_r.takes_unique() // fails
  // Finds method in OwnType, but can't turn &OwnType into &mut OwnType
  
  
  // what for calls to a and b ?
  a.takes_ref(); // again, it inserts & to the dereferenced type!
  b.takes_ref(); // ditto

  a.takes_unique(); // again, it inserts & to the dereferenced type!
  // b.takes_unique(); // can't insert &mut to b because it's not mut.
    
}

This step is incorrect. The fact that the impl block has a Self-type of OwnType is completely irrelevant to method search. All that matters is the receiver type that results after substituting the definition of Self, i.e. &OwnType in the case of a_method. Imagine that the compiler rewrites the code

impl OwnType { 
    fn a_method(&self) {...}
}

into a collection of every method it knows about, independent of impl (this is not real Rust syntax):

mod all_the_methods_in_the_world { 
    // all of your methods
    fn a_method(self: &OwnType) {...}

    // all of the methods from std too
    fn push<T>(self: Vec<T>, value: T) {...}
}

Method search just looks at all_the_methods_in_the_world regardless of where they came from (except that traits are only searched if in scope). In this case, method search finds a method with receiver type &OwnType (as the first candidate!) and calls it.

1 Like

I don't think I understand how is it irrelevant. I'm aware you understand this well; but is the snippet below and accompanying explanation sound to you?

let a = OwnType(); //implements a_method(&self)
let ar = &a;
ar.a_method() // takes &self
  • Goes through the method search procedure, where it gets &OwnType, &&OwnType, &mut &OwnType, OwnType, &OwnType (again), &mut OwnType and it then finds that OwnType is the winning candidate for .a_method()

  • Having obtained OwnType::a_method(..) it still has to see what to do with ar to match the signature of .a_method(&self).

In this case, it's nothing because ar matches exactly the &self.

But had we called a.a_method() instead, it would need to reference a i.e turn it into &a.

Is this part something we agree on, or where is my mistake?

I think we probably disagree, but I wonder how am I not following the reference.

Also, thanks for your patience.

What to do is already determined by where in the method search procedure it came from. If the search tried adding &, then the call to the found method must add &. If the search tried dereferencing, the call must dereference. There is no separate step to choose a coercion; the choice is made by the search process.

Consider this code:

trait HasRefMethod {
    fn by_ref(&self);
}
trait HasMoveMethod: Sized {   
    fn by_move(self);
}

struct Foo {}
impl HasRefMethod for Foo {
    fn by_ref(&self) {}
    // This could also be written as:
    //     fn by_ref(self: &Foo) {}
}
impl<'a> HasMoveMethod for &'a Foo {
    fn by_move(self) {}
    // This could also be written as:
    //     fn by_move(self: &'a Foo) {}
}

fn main() {
    let x = Foo {};
    x.by_ref(); // autoref
    x.by_move(); // also autoref
}

In the two method calls in this code, the receiver types are identical;[1] they are both &Foo. Method search will insert an implicit autoref &x to make the provided value type (Foo) match the methods’ receiver type (&Foo). It does not matter that one impl block is for Foo and one is for &Foo. Only the receiver type matters, and both receiver types are &Foo.


  1. except for lifetime ↩︎

I think I disagree with that. I don't think they are in sync but they are independent.

Take the snippet below:

let a = OwnType(); //implements a_method(&self)
a.a_method()

finds the method right in the calling type "T"; yet the method needs &self as per the signature, so it must add & to the variable a and so passes &a.

  • How does the process you described explain this case?

As for your first function, I agree with that one. And here is how I would use the procedure for those calls:

It finds that by_ref is on T, so it searches no further. But it needs an &self, so x will be &-ded so to speak or referenced. I think this is what you called "autoref" which I have not found much info about.

Here this is new for me, I have not read about implementing methods on &Ts but my interpretation would be that it must be adding a reference before starting the search (Which is not what the reference states unless I assume that the initial variable could be T or any reference they implicitly add. But I thought this wouldn't exist because then it would become infinite: why not to & it many more times if you implement it on &&&&&'a Foo?)

Just in case I'm using different terminology by receiver I mean the bit before the dot . in the call-expr.

I don't mean the self parameter name in the method's definition. I think those can be different, and they have to in cases such as my example above. I think what you are saying is that they are the same (maybe) but I challenge you to explain the snippet I gave at the top of this reply (or what is the same, your example with by_ref).

It does not find the method under the type OwnType. It finds the method with the receiver type &OwnType. The Self type OwnType is irrelevant except by contributing to defining the receiver type &Self = &OwnType.

The method search procedure tries T first, but then tries &T second. That's the part of the algorithm “Then, for each candidate T , add &T and &mut T to the list immediately after T.” This procedure always terminates because it doesn’t try adding & more than once (per candidate).

“Receiver” can mean either of these things, but when I’ve been talking about “receiver type” I mean the type in the method signature, the one that is in the end actually passed to the method, not the type of the expression in the receiver position of the method call expression.

So what you are saying is the actual candidates generated are to be matched with what's .method(self:___here___) ?

So in the case of a.a_method() we are simply selecting the second candidate i.e &T ?

So the receiver on the left, the part_here.method() is only needed to generate the list?

I believe this is what the algorithm (that's been referenced) says here:

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

In the above statement, "receiver of that type" means the type of self in the method.

I guess so, but it also defined receiver (which I included in the first method in OP.)

A method call consists of an expression (the receiver ) followed by a single dot, an expression path segment, and a parenthesised expression-list.

So my inference was, why would they define it as such, and then also use it for another thing (i.e self parameter)

It is matching the type of the receiver expression (the first thing it defines) in the caller, to the receiver type of the candidate methods. The sentence I quoted in the previous reply is referring to the latter.

search for a visible method with a receiver of that type

(bolding is mine)

By "with" here it means a method "that has" a receiver of that type. It does not mean "matches to a receiver" which might be how you interpreted it.

Yes, I managed to make a mess of the text. But that's correct.

1 Like

Just adding a minor comment, mostly to myself.

That keeps being surprising to me, although I now follow it, and it is a good model.

One example I tried today was:

fn main() {
    struct Hello;
    trait One { 
      fn x (&self){
          println!("hello one.")
      }
    }
    impl One for &Hello { } // i.e "One" expects "&&Hello"
    let a = Hello;
    // a.x();
    (&a).x(); // Generates &Hello, &&Hello,...
}

As expected, the commented line would fail; it does never generate a receiver of type &&Hello in the search process; whereas the line just below does.