From bebe83732fe66a85eaf5c5eeaeb006096881a904 Mon Sep 17 00:00:00 2001 From: Matthew Jasper Date: Wed, 24 Apr 2019 20:53:08 +0100 Subject: [PATCH] Add documentation for two-phase borrows --- src/SUMMARY.md | 1 + src/borrow_check/two_phase_borrows.md | 100 ++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/borrow_check/two_phase_borrows.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 099c65a7..477b9a70 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -76,6 +76,7 @@ - [Move paths](./borrow_check/moves_and_initialization/move_paths.md) - [MIR type checker](./borrow_check/type_check.md) - [Region inference](./borrow_check/region_inference.md) + - [Two-phase-borrows](./borrow_check/two_phase_borrows.md) - [Constant evaluation](./const-eval.md) - [miri const evaluator](./miri.md) - [Parameter Environments](./param_env.md) diff --git a/src/borrow_check/two_phase_borrows.md b/src/borrow_check/two_phase_borrows.md new file mode 100644 index 00000000..753ffa5b --- /dev/null +++ b/src/borrow_check/two_phase_borrows.md @@ -0,0 +1,100 @@ +# Two-phase borrows + +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: + +1. The autoref borrow when calling a method with a mutable reference receiver. +2. A mutable reborrow in function arguments. +3. The implicit mutable borrow in an overloaded compound assignment operator. + +To give some examples: + +```rust +// 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: + +```rust,ignore +// 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. + +[`AutoBorrow`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/adjustment/enum.AutoBorrow.html +[converted]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/hair/cx/expr/trait.ToBorrowKind.html#method.to_borrow_kind +[`BorrowKind`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/mir/enum.BorrowKind.html +[`GatherBorrows`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/mir/visit/trait.Visitor.html#method.visit_local +[`BorrowData`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/borrow_set/struct.BorrowData.html + +## Checking two-phase borrows + +Two-phase borrows are treated as if they were mutable borrows with the +following exceptions: + +1. 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. +2. At the reservation point we error if there are conflicting live *mutable* + borrows. And lint if there are any conflicting shared borrows. +3. 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 the [`Dominators`] for the MIR graph. +4. After the activation point, the two-phase borrow acts as a mutable borrow. + +[check]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/struct.MirBorrowckCtxt.html#method.check_activations +[`Dominators`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_data_structures/graph/dominators/struct.Dominators.html +[`is_active`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/path_utils/fn.is_active.html