How does Rust handle mutable reference of `Self` which is passed into a function, and the function itself is in the position as an argument of `Self`'s method that requires a mutable reference of `Self`?

Consider the following snippet:

#![allow(unused_variables)]

struct A;
struct B;

impl A {
    fn a(&mut self, b: B) {}
}

fn b(a: &mut A) -> B {
    B
}

fn main() {
    let mut a = A;

    a.a(b(&mut a));
}

// this works
fn main2() {
    let mut a = A;

    let b = b(&mut a);

    a.a(b);
}

Playground.

This is the error:

error[E0499]: cannot borrow `a` as mutable more than once at a time
  --> src/main.rs:21:11
   |
21 |     a.a(b(&mut a));
   |     - -   ^^^^^^ second mutable borrow occurs here
   |     | |
   |     | first borrow later used by call
   |     first mutable borrow occurs here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `playground` (bin "playground") due to 1 previous error

I am curious of there are any resources on how Rust evaluates function arguments? Because I would think Rust would be able to know that the &mut a passed into fn b will NOT outlive the value a of type &mut A in the method call a.a.

1 Like

Outliving is not the issue, the issue is that there are two mutable references at the same time.

a is evaluated first (implicitly it's treated as &mut a), b(&mut a) is evaluated next, and the two references live at the same time.

What is strange in that in some cases this is allowed, e.g.:

    let mut v = Vec::new();
    v.push(v.len());

There is an undocumented hack called "two-phase borrows" that allows this in some cases such as this, but apparently it doesn't apply to your example.

3 Likes

I almost wish this was not the case. It is confusing when you do something that works and you internalize this, and then when you repeat it in a slightly different way, it fails.

1 Like

I'm a bit surprized. Is fn b implicitly borrowing a for same lifetime as B? Because I don't see how otherwise it's two mutable borrowings.

1 Like

Agreed. I would also prefer these "ergonomics" improvements with complicated rules didn't exist. If the rules have to be too complicated for most people to understand (in this case they are not even documented in the reference AFAICT), the ergonomics are outweighed by confusion. In simple cases they create a false image as to how the language actually works.

3 Likes

Because A::a takes &mut self, the expression a.a(b(&mut a)) is implicitly translated into (&mut a).a(b(&mut a)). So it's something like this:

let x1 = &mut a;
let x2 = &mut a;
// x1 and x2 have to both be alive at this point
let x3 = b(x2);
A::a(x1, x3)
2 Likes

Obviously there is a reordering that would solve it (moving the let x1 after the let x3). But for me, it doesn't matter.

I think there are two choices:

  1. Find ways to avoid approaches that don't work and develop an intuition for them and for the approaches that do work. And when this fails, read the compiler error and roll with it, gradually adjusting your intuition.
  2. Try to understand exactly why every case fails or not, and try to learn the details of what the compiler is doing in every possible case. And try to keep up to date with every compiler improvement.

I choose the former. I can't possibly focus on these details and special cases while writing code. And to be honest, it is not a personal interest. I mainly want to use Rust to get things done. And it works quite well for that. The more I use it, the more I appreciate it. I rarely become frustrated now.

But I benefit very much from the people on this forum who choose the second approach and who do have that interest. Because I gradually learn more by reading the answers here. And when I get stuck, I almost always find the answer by searching here or in materials referenced here -- which are often written by the people here!

Thank you for your response.

Your example is okay but not the best because Vec::len() takes &self not &mut self.

Ah, so it expands into borrowings in the order of writing, and can't be this:

let x2 = &mut a;
// x1 and x2 have to both be alive at this point
let x3 = b(x2);
let x1 = &mut a; // moved below
A::a(x1, x3)

right?

Yes, you are right. That is why two-phase borrows don't apply to your original example.

The way this works is that v is first temporarily taken as a shared borrow, and then upgraded to a mutable borrow after the arguments are evaluated. However, if you are explicit about the mutable borrow, it no longer compiles:

    let mut v = Vec::new();
    (&mut v).push(v.len());
error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable

Yes. In (expr1).method(expr2), expr1 is evaluated before expr2.

2 Likes

Ahh, thank you @tczajka for your clarifications, now I understand better.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.