Skip to content

Commit dca6649

Browse files
matthewjaspermark-i-m
authored andcommitted
Add documentation for two-phase borrows
1 parent ad07f7f commit dca6649

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
- [Move paths](./borrow_check/moves_and_initialization/move_paths.md)
7777
- [MIR type checker](./borrow_check/type_check.md)
7878
- [Region inference](./borrow_check/region_inference.md)
79+
- [Two-phase-borrows](./borrow_check/two_phase_borrows.md)
7980
- [Constant evaluation](./const-eval.md)
8081
- [miri const evaluator](./miri.md)
8182
- [Parameter Environments](./param_env.md)

src/borrow_check/two_phase_borrows.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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

Comments
 (0)