Your pseudo-code is pretty much correct. For this example, suppose we had a method call foo.bar()
where foo: T
. I'm going to use the fully qualified syntax (FQS) to be unambiguous about what type the method is being called with, e.g. A::bar(foo)
or A::bar(&***foo)
. I'm just going to write a pile of random capital letters, each one is just some arbitrary type/trait, except T
is always the type of the original variable foo
that the method is called on.
The core of the algorithm is:
- For each "dereference step"
U
(that is, set U = T
and then U = *T
, ...)
- if there's a method
bar
where the receiver type (the type of self
in the method) matches U
exactly , use it (a "by value method")
- otherwise, add one auto-ref (take
&
or &mut
of the receiver), and, if some method's receiver matches &U
, use it (an "autorefd method")
Notably, everything considers the "receiver type" of the method, not the Self
type of the trait, i.e. impl ... for Foo { fn method(&self) {} }
thinks about &Foo
when matching the method, and fn method2(&mut self)
would think about &mut Foo
when matching.
It is an error if there's ever multiple trait methods valid in the inner steps (that is, there can be only be zero or one trait methods valid in each of 1. or 2., but there can be one valid for each: the one from 1 will be taken first), and inherent methods take precedence over trait ones. It's also an error if we get to the end of the loop without finding anything that matches. It is also an error to have recursive Deref
implementations, which make the loop infinite (they'll hit the "recursion limit").
These rules seem to do-what-I-mean in most circumstances, although having the ability to write the unambiguous FQS form is very useful in some edge cases, and for sensible error messages for macro-generated code.
Only one auto-reference is added because
- if there was no bound, things get bad/slow, since every type can have an arbitrary number of references taken
- taking one reference
&foo
retains a strong connection to foo
(it is the address of foo
itself), but taking more starts to lose it: &&foo
is the address of some temporary variable on the stack that stores &foo
.
Examples
Suppose we have a call foo.refm()
, if foo
has type:
X
, then we start with U = X
, refm
has receiver type &...
, so step 1 doesn't match, taking an auto-ref gives us &X
, and this does match (with Self = X
), so the call is RefM::refm(&foo)
&X
, starts with U = &X
, which matches &self
in the first step (with Self = X
), and so the call is RefM::refm(foo)
&&&&&X
, this doesn't match either step (the trait isn't implemented for &&&&X
or &&&&&X
), so we dereference once to get U = &&&&X
, which matches 1 (with Self = &&&X
) and the call is RefM::refm(*foo)
Z
, doesn't match either step so it is dereferenced once, to get Y
, which also doesn't match, so it's dereferenced again, to get X
, which doesn't match 1, but does match after autorefing, so the call is RefM::refm(&**foo)
.
&&A
, the 1. doesn't match and neither does 2. since the trait is not implemented for &A
(for 1) or &&A
(for 2), so it is dereferenced to &A
, which matches 1., with Self = A
Suppose we have foo.m()
, and that A
isn't Copy
, if foo
has type:
A
, then U = A
matches self
directly so the call is M::m(foo)
with Self = A
&A
, then 1. doesn't match, and neither does 2. (neither &A
nor &&A
implement the trait), so it is dereferenced to A
, which does match, but M::m(*foo)
requires taking A
by value and hence moving out of foo
, hence the error.
&&A
, 1. doesn't match, but autorefing gives &&&A
, which does match, so the call is M::m(&foo)
with Self = &&&A
.
(This answer is based on the code, and is reasonably close to the (slightly outdated) README. Niko Matsakis, the main author of this part of the compiler/language, also glanced over this answer.)
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…