Skip to content

Commit 3f3f0e3

Browse files
authored
Merge pull request #234 from mark-i-m/mir_borrowck
Fill out the borrowck chapter a bit more
2 parents c3928dd + a69982d commit 3f3f0e3

File tree

2 files changed

+188
-30
lines changed

2 files changed

+188
-30
lines changed

src/borrow_check.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,14 @@ the [`mir_borrowck`] query.
5151
the purpose of this type check is to determine all of the constraints between
5252
different regions.
5353
- Next, we do [region inference](borrow_check/region_inference.html), which computes
54-
the values of each region — basically, points in the control-flow graph.
54+
the values of each region — basically, the points in the control-flow graph where
55+
each lifetime must be valid according to the constraints we collected.
5556
- At this point, we can compute the "borrows in scope" at each point.
5657
- Finally, we do a second walk over the MIR, looking at the actions it
5758
does and reporting errors. For example, if we see a statement like
5859
`*a + 1`, then we would check that the variable `a` is initialized
5960
and that it is not mutably borrowed, as either of those would
60-
require an error to be reported.
61-
- Doing this check requires the results of all the previous analyses.
61+
require an error to be reported. Doing this check requires the results of all
62+
the previous analyses.
6263

6364
[`replace_regions_in_mir`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/fn.replace_regions_in_mir.html

src/borrow_check/region_inference.md

+184-27
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ deprecated once they become the standard kind of lifetime.)
99

1010
The MIR-based region analysis consists of two major functions:
1111

12-
- `replace_regions_in_mir`, invoked first, has two jobs:
12+
- [`replace_regions_in_mir`], invoked first, has two jobs:
1313
- First, it finds the set of regions that appear within the
1414
signature of the function (e.g., `'a` in `fn foo<'a>(&'a u32) {
1515
... }`). These are called the "universal" or "free" regions – in
@@ -21,49 +21,67 @@ The MIR-based region analysis consists of two major functions:
2121
not of much interest. The intention is that – eventually – they
2222
will be "erased regions" (i.e., no information at all), since we
2323
won't be doing lexical region inference at all.
24-
- `compute_regions`, invoked second: this is given as argument the
24+
- [`compute_regions`], invoked second: this is given as argument the
2525
results of move analysis. It has the job of computing values for all
2626
the inference variables that `replace_regions_in_mir` introduced.
27-
- To do that, it first runs the [MIR type checker](#mirtypeck). This
27+
- To do that, it first runs the [MIR type checker]. This
2828
is basically a normal type-checker but specialized to MIR, which
29-
is much simpler than full Rust of course. Running the MIR type
29+
is much simpler than full Rust, of course. Running the MIR type
3030
checker will however create **outlives constraints** between
3131
region variables (e.g., that one variable must outlive another
3232
one) to reflect the subtyping relationships that arise.
3333
- It also adds **liveness constraints** that arise from where variables
3434
are used.
35-
- More details to come, though the [NLL RFC] also includes fairly thorough
36-
(and hopefully readable) coverage.
35+
- After this, we create a [`RegionInferenceContext`] with the constraints we
36+
have computed and the inference variables we introduced and use the
37+
[`solve`] method to infer values for all region inference varaibles.
38+
- The [NLL RFC] also includes fairly thorough (and hopefully readable)
39+
coverage.
3740

3841
[fvb]: ../appendix/background.html#free-vs-bound
42+
[`replace_regions_in_mir`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/fn.replace_regions_in_mir.html
43+
[`compute_regions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/fn.compute_regions.html
44+
[`RegionInferenceContext`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html
45+
[`solve`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.solve
3946
[NLL RFC]: http://rust-lang.github.io/rfcs/2094-nll.html
47+
[MIR type checker]: ./type_check.md
4048

4149
## Universal regions
4250

43-
*to be written* – explain the `UniversalRegions` type
51+
The [`UnversalRegions`] type represents a collection of _universal_ regions
52+
corresponding to some MIR `DefId`. It is constructed in
53+
[`replace_regions_in_mir`] when we replace all regions with fresh inference
54+
variables. [`UniversalRegions`] contains indices for all the free regions in
55+
the given MIR along with any relationships that are _known_ to hold between
56+
them (e.g. implied bounds, where clauses, etc.).
4457

45-
## Region variables and constraints
58+
For example, given the MIR for the following function:
4659

47-
*to be written* – describe the `RegionInferenceContext` and
48-
the role of `liveness_constraints` vs other `constraints`, plus
49-
50-
## Closures
60+
```rust
61+
fn foo<'a>(x: &'a u32) {
62+
// ...
63+
}
64+
```
5165

52-
*to be written*
66+
we would create a universal region for `'a` and one for `'static`. There may
67+
also be some complications for handling closures, but we will ignore those for
68+
the moment.
5369

54-
<a name="mirtypeck"></a>
70+
TODO: write about _how_ these regions are computed.
5571

56-
## The MIR type-check
72+
[`UniversalRegions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/universal_regions/struct.UniversalRegions.html
5773

58-
## Representing the "values" of a region variable
74+
## Region variables
5975

60-
The value of a region can be thought of as a **set**; we call the
61-
domain of this set a `RegionElement`. In the code, the value for all
62-
regions is maintained in
63-
[the `rustc_mir::borrow_check::nll::region_infer` module][ri]. For
64-
each region we maintain a set storing what elements are present in its
65-
value (to make this efficient, we give each kind of element an index,
66-
the `RegionElementIndex`, and use sparse bitsets).
76+
The value of a region can be thought of as a **set**. This set contains all
77+
points in the MIR where the region is valid along with any regions that are
78+
outlived by this region (e.g. if `'a: 'b`, then `end('b)` is in the set for
79+
`'a`); we call the domain of this set a `RegionElement`. In the code, the value
80+
for all regions is maintained in [the
81+
`rustc_mir::borrow_check::nll::region_infer` module][ri]. For each region we
82+
maintain a set storing what elements are present in its value (to make this
83+
efficient, we give each kind of element an index, the `RegionElementIndex`, and
84+
use sparse bitsets).
6785

6886
[ri]: https://github.com/rust-lang/rust/tree/master/src/librustc_mir/borrow_check/nll/region_infer/
6987

@@ -83,12 +101,148 @@ The kinds of region elements are as follows:
83101
for details on placeholders, see the section
84102
[placeholders and universes](#placeholder).
85103

86-
## Causal tracking
104+
## Constraints
105+
106+
Before we can infer the value of regions, we need to collect constraints on the
107+
regions. There are two primary types of constraints.
108+
109+
1. Outlives constraints. These are constraints that one region outlives another
110+
(e.g. `'a: 'b`). Outlives constraints are generated by the [MIR type
111+
checker].
112+
2. Liveness constraints. Each region needs to be live at points where it can be
113+
used. These constraints are collected by [`generate_constraints`].
114+
115+
[`generate_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/constraint_generation/fn.generate_constraints.html
116+
117+
## Inference Overview
118+
119+
So how do we compute the contents of a region? This process is called _region
120+
inference_. The high-level idea is pretty simple, but there are some details we
121+
need to take care of.
122+
123+
Here is the high-level idea: we start off each region with the MIR locations we
124+
know must be in it from the liveness constraints. From there, we use all of the
125+
outlives constraints computed from the type checker to _propagate_ the
126+
constraints: for each region `'a`, if `'a: 'b`, then we add all elements of
127+
`'b` to `'a`, including `end('b)`. This all happens in
128+
[`propagate_constraints`].
129+
130+
Then, we will check for errors. We first check that type tests are satisfied by
131+
calling [`check_type_tests`]. This checks constraints like `T: 'a`. Second, we
132+
check that universal regions are not "too big". This is done by calling
133+
[`check_universal_regions`]. This checks that for each region `'a` if `'a`
134+
contains the element `end('b)`, then we must already know that `'a: 'b` holds
135+
(e.g. from a where clause). If we don't already know this, that is an error...
136+
well, almost. There is some special handling for closures that we will discuss
137+
later.
138+
139+
### Example
140+
141+
Consider the following example:
87142

88-
*to be written* – describe how we can extend the values of a variable
89-
with causal tracking etc
143+
```rust,ignore
144+
fn foo<'a, 'b>(x: &'a usize) -> &'b usize {
145+
x
146+
}
147+
```
148+
149+
Clearly, this should not compile because we don't know if `'a` outlives `'b`
150+
(if it doesn't then the return value could be a dangling reference).
90151

91-
<a name="placeholder"></a>
152+
Let's back up a bit. We need to introduce some free inference variables (as is
153+
done in [`replace_regions_in_mir`]). This example doesn't use the exact regions
154+
produced, but it (hopefully) is enough to get the idea across.
155+
156+
```rust,ignore
157+
fn foo<'a, 'b>(x: &'a /* '#1 */ usize) -> &'b /* '#3 */ usize {
158+
x // '#2, location L1
159+
}
160+
```
161+
162+
Some notation: `'#1`, `'#3`, and `'#2` represent the universal regions for the
163+
argument, return value, and the expression `x`, respectively. Additionally, I
164+
will call the location of the expression `x` `L1`.
165+
166+
So now we can use the liveness constraints to get the following starting points:
167+
168+
Region | Contents
169+
--------|----------
170+
'#1 |
171+
'#2 | `L1`
172+
'#3 | `L1`
173+
174+
Now we use the outlives constraints to expand each region. Specifically, we
175+
know that `'#2: '#3` ...
176+
177+
Region | Contents
178+
--------|----------
179+
'#1 | `L1`
180+
'#2 | `L1, end('#3) // add contents of '#3 and end('#3)`
181+
'#3 | `L1`
182+
183+
... and `'#1: '#2`, so ...
184+
185+
Region | Contents
186+
--------|----------
187+
'#1 | `L1, end('#2), end('#3) // add contents of '#2 and end('#2)`
188+
'#2 | `L1, end('#3)`
189+
'#3 | `L1`
190+
191+
Now, we need to check that no regions were too big (we don't have any type
192+
tests to check in this case). Notice that `'#1` now contains `end('#3)`, but
193+
we have no `where` clause or implied bound to say that `'a: 'b`... that's an
194+
error!
195+
196+
### Some details
197+
198+
The [`RegionInferenceContext`] type contains all of the information needed to
199+
do inference, including the universal regions from [`replace_regions_in_mir`] and
200+
the constraints computed for each region. It is constructed just after we
201+
compute the liveness constraints.
202+
203+
Here are some of the fields of the struct:
204+
205+
- [`constraints`]: contains all the outlives constraints.
206+
- [`liveness_constraints`]: contains all the liveness constraints.
207+
- [`universal_regions`]: contains the `UniversalRegions` returned by
208+
[`replace_regions_in_mir`].
209+
- [`universal_region_relations`]: contains relations known to be true about
210+
universal regions. For example, if we have a where clause that `'a: 'b`, that
211+
relation is assumed to be true while borrow checking the implementation (it
212+
is checked at the caller), so `universal_region_relations` would contain `'a:
213+
'b`.
214+
- [`type_tests`]: contains some constraints on types that we must check after
215+
inference (e.g. `T: 'a`).
216+
- [`closure_bounds_mapping`]: used for propagating region constraints from
217+
closures back out to the creater of the closure.
218+
219+
[`constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.constraints
220+
[`liveness_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.liveness_constraints
221+
[`universal_regions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.universal_regions
222+
[`universal_region_relations`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.universal_region_relations
223+
[`type_tests`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.type_tests
224+
[`closure_bounds_mapping`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.closure_bounds_mapping
225+
226+
TODO: should we discuss any of the others fields? What about the SCCs?
227+
228+
Ok, now that we have constructed a `RegionInferenceContext`, we can do
229+
inference. This is done by calling the [`solve`] method on the context. This
230+
is where we call [`propagate_constraints`] and then check the resulting type
231+
tests and universal regions, as discussed above.
232+
233+
[`propagate_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.propagate_constraints
234+
[`check_type_tests`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.check_type_tests
235+
[`check_universal_regions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.check_universal_regions
236+
237+
## Closures
238+
239+
When we are checking the type tests and universal regions, we may come across a
240+
constraint that we can't prove yet if we are in a closure body! However, the
241+
necessary constraints may actually hold (we just don't know it yet). Thus, if
242+
we are inside a closure, we just collect all the constraints we can't prove yet
243+
and return them. Later, when we are borrow check the MIR node that created the
244+
closure, we can also check that these constraints hold. At that time, if we
245+
can't prove they hold, we report an error.
92246

93247
## Placeholders and universes
94248

@@ -534,3 +688,6 @@ Now constraint propagation is done, but when we check the outlives
534688
relationships, we find that `V2` includes this new element `placeholder(1)`,
535689
so we report an error.
536690

691+
## Borrow Checker Errors
692+
693+
TODO: we should discuss how to generate errors from the results of these analyses.

0 commit comments

Comments
 (0)