Rust method calls are desugared to fully-qualified-syntax and function parameters are evaluated left to right. Why then does s.by_mut(s.by_ref(()))
compile while other
expressions don't?
struct S;
impl S {
fn by_ref(&self, _arg: ()) {}
fn by_mut(&mut self, _arg: ()) {}
}
fn foo() {
let mut s = S;
s.by_mut(s.by_ref(())); // compiles, but why?
s.by_mut(s.by_mut(())); // [E0499] cannot borrow `s` as mutable more than once at a time
// expected desugarings as per https://doc.rust-lang.org/reference/expressions/call-expr.html#r-expr.call.desugar
S::by_mut(&mut s, S::by_ref(&s, ())); // [E0502] cannot borrow `s` as immutable because it is also borrowed as mutable
S::by_mut(&mut s, S::by_mut(&mut s, ())); // [E0499] cannot borrow `s` as mutable more than once at a time
(&mut s).by_mut((&s).by_ref(())); // [E0502] cannot borrow `s` as immutable because it is also borrowed as mutable
(&mut s).by_mut((&mut s).by_mut(())); // [E0499] cannot borrow `s` as mutable more than once at a time
}
Switching by_ref
to take self
and using #[derive(Clone, Copy)]
, or using a type like String
rather than ()
changes nothing.
Switching the order of by_ref
and by_mut
gives similar results. Notably s.by_ref(s.by_mut(()))
does not compile.
fn bar() {
let mut s = S;
s.by_ref(s.by_mut(())); // [E0502] cannot borrow `s` as mutable because it is also borrowed as immutable
s.by_ref(s.by_ref(())); // very much okay
// expected desugarings
S::by_ref(&s, S::by_mut(&mut s, ())); // [E0502] cannot borrow `s` as mutable because it is also borrowed as immutable
S::by_ref(&s, S::by_ref(&s, ())); // very much okay
(&s).by_ref((&mut s).by_mut(())); // [E0502] cannot borrow `s` as mutable because it is also borrowed as immutable
(&s).by_ref((&s).by_ref(())); // very much okay
}
I expected s.by_mut(s.by_ref(()))
, s.by_mut(s.by_mut(()))
, and s.by_ref(s.by_mut(()))
to either all compile or all not to compile.
You've discovered a rarely known Rust feature called two-phase borrows.
This feature is not only rarely known, but also barely documented. The only documentation I know of is the RFC that introduced it.
In short, when you have s.by_mut()
, there isn't a normal reference there. It's a special kind of reference that has two phases: reservation and activation.
When the autoref executes (before the arguments are evaluated), the reference is only reserved. At this point in time, it behaves like a shared reference.
Then, after the arguments are evaluated, the reference is activated and becomes a normal mutable reference.
Only method calls can do that. There is no way to mimic this kind of borrow manually, which is why your desugarings don't work. s.by_mut(s.by_mut())
doesn't work either, because the reserved borrow for the first by_mut()
(acting as a shared reference) prevents the second by_mut()
from activating. Similar thing for s.by_ref(s.by_mut())
.
The reason for this feature is to make code like vec.push(vec.len())
to work. You can read more in the RFC I linked.