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:
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 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:
is_active
) if we're at such a point by using the Dominators
for the MIR graph.