The easiest way to illustrate this is within the context of the sample code x.foo(y)
.
x
here is the receiver. Given the type of x
, the compiler has to figure out what function foo
is. It attempts to locate foo
on x
's type, and additionally by auto-dereferencing x
, which is what you are asking about. This process considers the type of x
as well as every type that is encountered by dereferencing x
as many times as possible. (It also considers &
and possibly &mut
variants of these types, depending on the mutability of x
or its reference type.)
If exactly one foo
is found, then the call is transformed to T::foo(z, y)
where T
is the type the foo
method was located on, and z
is whatever sequence of dereferences (plus one final, optional &
or &mut
) results in a T
, &T
, or &mut T
, as required by T::foo
.
What about y
, though? Arguments undergo coercion, which is a different thing altogether. The linked documentation lists all of the possible coercions, which I will omit for the sake of brevity, but notably absent are &T
to T
and &mut T
to T
, one of which would be required to make map.entry(i)
work in your sample code. The explicit dereference is therefore required.
Note that one of the items present in the list of coercions is:
&T
or &mut T
to &U
if T
implements Deref<Target = U>
.
Observe that this doesn't actually dereference a reference but rather converts the reference to another type of reference! That is, the coercion is &T
to &U
, not &T
to U
. This is what allows you to give &String
to an argument that expects a &str
, for example (String
implements Deref<Target = str>
), but this rule doesn't provide coercion from &mut i32
to i32
as would be required to make map.entry(i)
work.
for i in v
is a completely different scenario; this works simply because &mut Vec<T>
implements IntoIterator
.
So the answer as to why you don't have to dereference v
is because you are using it in receiver position, where auto-dereference happens. Function arguments instead undergo coercion, which has an entirely different set of rules.
Now having said that, in your code v
is already a reference to a Vec
, and so v.is_empty()
could be explained through coercion as well, as this method comes from slices, to which Vec
automatically dereferences. However, the overall point still stands -- you cannot conflate auto-dereferencing with deref coercion. This is one instance where they would happen to do the same thing, but in many other cases they would not.