|
| 1 | +# Two-phase borrows |
| 2 | + |
| 3 | +Two-phase borrows are a more permissive version of mutable borrows that allow |
| 4 | +nested method calls such as `vec.push(vec.len())`. Such borrows first act as |
| 5 | +shared borrows in a "reservation" phase and can later be "activated" into a |
| 6 | +full mutable borrow. |
| 7 | + |
| 8 | +Only certain implicit mutable borrows can be two-phase, any `&mut` or `ref mut` |
| 9 | +in the source code is never a two-phase borrow. The cases where we generate a |
| 10 | +two-phase borrow are: |
| 11 | + |
| 12 | +1. The autoref borrow when calling a method with a mutable reference receiver. |
| 13 | +2. A mutable reborrow in function arguments. |
| 14 | +3. The implicit mutable borrow in an overloaded compound assignment operator. |
| 15 | + |
| 16 | +To give some examples: |
| 17 | + |
| 18 | +```rust |
| 19 | +// In the source code |
| 20 | + |
| 21 | +// Case 1: |
| 22 | +let mut v = Vec::new(); |
| 23 | +v.push(v.len()); |
| 24 | +let r = &mut Vec::new(); |
| 25 | +r.push(r.len()); |
| 26 | + |
| 27 | +// Case 2: |
| 28 | +std::mem::replace(r, vec![1, r.len()]); |
| 29 | + |
| 30 | +// Case 3: |
| 31 | +let mut x = std::num::Wrapping(2); |
| 32 | +x += x; |
| 33 | +``` |
| 34 | + |
| 35 | +Expanding these enough to show the two-phase borrows: |
| 36 | + |
| 37 | +```rust,ignore |
| 38 | +// Case 1: |
| 39 | +let mut v = Vec::new(); |
| 40 | +let temp1 = &two_phase v; |
| 41 | +let temp2 = v.len(); |
| 42 | +Vec::push(temp1, temp2); |
| 43 | +let r = &mut Vec::new(); |
| 44 | +let temp3 = &two_phase *r; |
| 45 | +let temp4 = r.len(); |
| 46 | +Vec::push(temp3, temp4); |
| 47 | +
|
| 48 | +// Case 2: |
| 49 | +let temp5 = &two_phase *r; |
| 50 | +let temp6 = vec![1, r.len()]; |
| 51 | +std::mem::replace(temp5, temp6); |
| 52 | +
|
| 53 | +// Case 3: |
| 54 | +let mut x = std::num::Wrapping(2); |
| 55 | +let temp7 = &two_phase x; |
| 56 | +let temp8 = x; |
| 57 | +std::ops::AddAssign::add_assign(temp7, temp8); |
| 58 | +``` |
| 59 | + |
| 60 | +Whether a borrow can be two-phase is tracked by a flag on the [`AutoBorrow`] |
| 61 | +after type checking, which is then [converted] to a [`BorrowKind`] during MIR |
| 62 | +construction. |
| 63 | + |
| 64 | +Each two-phase borrow is assigned to a temporary that is only used once. As |
| 65 | +such we can define: |
| 66 | + |
| 67 | +* The point where the temporary is assigned to is called the *reservation* |
| 68 | + point of the two-phase borrow. |
| 69 | +* The point where the temporary is used, which is effectively always a |
| 70 | + function call, is called the *activation* point. |
| 71 | + |
| 72 | +The activation points are found using the [`GatherBorrows`] visitor. The |
| 73 | +[`BorrowData`] then holds both the reservation and activation points for the |
| 74 | +borrow. |
| 75 | + |
| 76 | +[`AutoBorrow`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/adjustment/enum.AutoBorrow.html |
| 77 | +[converted]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/hair/cx/expr/trait.ToBorrowKind.html#method.to_borrow_kind |
| 78 | +[`BorrowKind`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/mir/enum.BorrowKind.html |
| 79 | +[`GatherBorrows`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/mir/visit/trait.Visitor.html#method.visit_local |
| 80 | +[`BorrowData`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/borrow_set/struct.BorrowData.html |
| 81 | + |
| 82 | +## Checking two-phase borrows |
| 83 | + |
| 84 | +Two-phase borrows are treated as if they were mutable borrows with the |
| 85 | +following exceptions: |
| 86 | + |
| 87 | +1. At every location in the MIR we [check] if any two-phase borrows are |
| 88 | + activated at this location. If a live two phase borrow is activated at a |
| 89 | + location, then we check that there are no borrows that conflict with the |
| 90 | + two-phase borrow. |
| 91 | +2. At the reservation point we error if there are conflicting live *mutable* |
| 92 | + borrows. And lint if there are any conflicting shared borrows. |
| 93 | +3. Between the reservation and the activation point, the two-phase borrow acts |
| 94 | + as a shared borrow. We determine (in [`is_active`]) if we're at such a point |
| 95 | + by using the [`Dominators`] for the MIR graph. |
| 96 | +4. After the activation point, the two-phase borrow acts as a mutable borrow. |
| 97 | + |
| 98 | +[check]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/struct.MirBorrowckCtxt.html#method.check_activations |
| 99 | +[`Dominators`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_data_structures/graph/dominators/struct.Dominators.html |
| 100 | +[`is_active`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/path_utils/fn.is_active.html |
0 commit comments