I've seen this in a comment in StackOverflow, and can't seem to understand it...
I was under the impression that a simple trait like this:
trait IntoFoo {
fn into_foo(self);
}
could always be called either like this:
v.into_foo();
or like this (Fully Qualified Syntax - FQS):
IntoFoo::into_foo(v);
But, if we happen to implement it for a &mut C:
struct C;
impl<'a> IntoFoo for &'a mut C {
fn into_foo(self) {
println!("IntoFoo::into_foo for &mut C");
}
}
Well, the first form can be called more than once, but why the second can't??
fn do_it(v: &mut C) {
v.into_foo();
v.into_foo();
IntoFoo::into_foo(v);
// IntoFoo::into_foo(v);
}
fn main() {
let mut c = C;
do_it(&mut c);
}
Why does only the fully qualified syntax move the value?
Shouldn't they be synonyms, thus having the same effects?
error[E0382]: use of moved value: `v`
--> src/main.rs:17:23
|
13 | fn do_it(v: &mut C) {
| - move occurs because `v` has type `&mut C`, which does not implement the `Copy` trait
...
16 | IntoFoo::into_foo(v);
| -------------------- `v` moved due to this method call
17 | IntoFoo::into_foo(v);
| ^ value used here after move
|
note: this function takes ownership of the receiver `self`, which moves `v`
--> src/main.rs:2:17
|
2 | fn into_foo(self);
| ^^^^
If you're using Rust analyzer (and you probably should). There's an option called "Reborrow Hints" that will show where the compiler inserts reborrows. The lifetime elision hints can be helpful to see how the compiler adds lifetimes (not related to this question).
The rules for method calls are documented; the somewhat simplified TL;DR is that the compiler tries really hard to find a matching method, and references/dereferences as much as needed. (The actual rules are a bit more nuanced.)
In contrast, UFCS doesn't do any of that, it's just like a regular function call without a special self parameter.
So, in the first step of the method call syntax, when the compiler builds a list of candidate receiver types, it will dereference that &mut C into a C, then add both &C and &mut C... I think I got it!
And then, when it does find a perfect receiver match in &mut C, it in fact reborrows it, i.e. it in fact dereferenced it to build the list, right?
Very cool!!
Well, when using the FQS the compiler disables all this mechanism and calls it directly, ok till here. But the error still doesn't make sense to me. Actually I never really understood this: a self receiver, but you actually implement that for a &mut.....
The error states "this function takes ownership of the receiver self", granted it is writtenself, but the actual receiver is still &mut self, isn't it?
The candidates for the method call v.into_foo() are the following, in order:
IntoFoo::into_foo(v) <- works
IntoFoo::into_foo(&v) <- doesn't work
IntoFoo::into_foo(&mut v) <- doesn't work
IntoFoo::into_foo(*v) <- doesn't work
IntoFoo::into_foo(&*v) <- doesn't work
IntoFoo::into_foo(&mut *v) <- works
It would seem that according to the documentation the first one should be called (IntoFoo::into_foo(v)), whereas in reality the last one is called (IntoFoo::into_foo(&mut *v)).
So this interpretation doesn't match reality.
Interpretation 2
Now I'm going for a more literal interpretation of what the page says.
First the list of types is formed, in order:
&mut C <- works
&&mut C
&mut &mut C
C
&C
&mut C <- works
&mut C appears twice in the list. Whatever, that type is selected. Now we find methods that match that type, and there is only one: IntoFoo::into_foo(&mut C).
But under this second interpretation the documentation doesn't specify how the method call is translated into a function call. Is it IntoFoo::into_foo(v) or IntoFoo::into_foo(&mut *v)? How is this decided?