rustreference

Why is `&self` allowed in the parameters of `&mut self` methods, but not `&mut self`?


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
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=180ec14bab083b4cfe9b73dd3a1ede56

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.


Solution

  • 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.