#![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);
}
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 valuea of type &mut A in the method call a.a.
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.
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.
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:
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.
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!
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.