Two-phase borrows are a more permissive version of mutable borrows that allow
nested method calls such as vec.push(vec.len())
. Such borrows first act as
shared borrows in a "reservation" phase and can later be "activated" into a
full mutable borrow.
Only certain implicit mutable borrows can be two-phase, any &mut
or ref mut
in the source code is never a two-phase borrow. The cases where we generate a
two-phase borrow are:
- The autoref borrow when calling a method with a mutable reference receiver.
- A mutable reborrow in function arguments.
- The implicit mutable borrow in an overloaded compound assignment operator.
To give some examples:
// In the source code
// Case 1:
let mut v = Vec::new();
v.push(v.len());
let r = &mut Vec::new();
r.push(r.len());
// Case 2:
std::mem::replace(r, vec![1, r.len()]);
// Case 3:
let mut x = std::num::Wrapping(2);
x += x;
Expanding these enough to show the two-phase borrows:
// Case 1:
let mut v = Vec::new();
let temp1 = &two_phase v;
let temp2 = v.len();
Vec::push(temp1, temp2);
let r = &mut Vec::new();
let temp3 = &two_phase *r;
let temp4 = r.len();
Vec::push(temp3, temp4);
// Case 2:
let temp5 = &two_phase *r;
let temp6 = vec![1, r.len()];
std::mem::replace(temp5, temp6);
// Case 3:
let mut x = std::num::Wrapping(2);
let temp7 = &two_phase x;
let temp8 = x;
std::ops::AddAssign::add_assign(temp7, temp8);
Whether a borrow can be two-phase is tracked by a flag on the AutoBorrow
after type checking, which is then converted to a BorrowKind
during MIR
construction.
Each two-phase borrow is assigned to a temporary that is only used once. As such we can define:
- The point where the temporary is assigned to is called the reservation point of the two-phase borrow.
- The point where the temporary is used, which is effectively always a function call, is called the activation point.
The activation points are found using the GatherBorrows
visitor. The
BorrowData
then holds both the reservation and activation points for the
borrow.
Two-phase borrows are treated as if they were mutable borrows with the following exceptions:
- At every location in the MIR we check if any two-phase borrows are activated at this location. If a live two phase borrow is activated at a location, then we check that there are no borrows that conflict with the two-phase borrow.
- At the reservation point we error if there are conflicting live mutable borrows. And lint if there are any conflicting shared borrows.
- Between the reservation and the activation point, the two-phase borrow acts
as a shared borrow. We determine (in
is_active
) if we're at such a point by using theDominators
for the MIR graph. - After the activation point, the two-phase borrow acts as a mutable borrow.