Skip to content

Commit ae4bc4d

Browse files
committed
add content
1 parent f7c64b6 commit ae4bc4d

File tree

6 files changed

+218
-24
lines changed

6 files changed

+218
-24
lines changed

src/SUMMARY.md

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
- [MIR-based region checking (NLL)](./mir/regionck.md)
5555
- [MIR optimizations](./mir/optimizations.md)
5656
- [The borrow checker](./borrow_check.md)
57+
- [Tracking moves and initialization](./borrow_check/moves_and_initialization.md)
58+
- [Move paths](./borrow_check/moves_and_initialization/move_paths.md)
59+
- [MIR type checker](./borrow_check/type_check.md)
5760
- [Region inference](./borrow_check/region_inference.md)
5861
- [Constant evaluation](./const-eval.md)
5962
- [miri const evaluator](./miri.md)

src/borrow_check.md

+26-22
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ enforcing a number of properties:
1414
At the time of this writing, the code is in a state of transition. The
1515
"main" borrow checker still works by processing [the HIR](hir.html),
1616
but that is being phased out in favor of the MIR-based borrow checker.
17-
Doing borrow checking on MIR has two key advantages:
17+
Accordingly, this documentation focuses on the new, MIR-based borrow
18+
checker.
19+
20+
Doing borrow checking on MIR has several advantages:
1821

1922
- The MIR is *far* less complex than the HIR; the radical desugaring
2023
helps prevent bugs in the borrow checker. (If you're curious, you
@@ -30,30 +33,31 @@ Doing borrow checking on MIR has two key advantages:
3033

3134
The borrow checker source is found in
3235
[the `rustc_mir::borrow_check` module][b_c]. The main entry point is
33-
the `mir_borrowck` query. At the time of this writing, MIR borrowck can operate
34-
in several modes, but this text will describe only the mode when NLL is enabled
35-
(what you get with `#![feature(nll)]`).
36-
37-
[b_c]: https://github.com/rust-lang/rust/tree/master/src/librustc_mir/borrow_check
36+
the [`mir_borrowck`] query.
3837

39-
The overall flow of the borrow checker is as follows:
38+
[b_c]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/index.html
39+
[`mir_borrowck`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/fn.mir_borrowck.html
4040

4141
- We first create a **local copy** C of the MIR. In the coming steps,
4242
we will modify this copy in place to modify the types and things to
4343
include references to the new regions that we are computing.
44-
- We then invoke `nll::replace_regions_in_mir` to modify this copy C.
45-
Among other things, this function will replace all of the regions in
44+
- We then invoke [`replace_regions_in_mir`] to modify this copy C.
45+
Among other things, this function will replace all of the [regions](./appendix/glossary.html) in
4646
the MIR with fresh [inference variables](./appendix/glossary.html).
47-
- (More details can be found in [the regionck section](./mir/regionck.html).)
48-
- Next, we perform a number of [dataflow
49-
analyses](./appendix/background.html#dataflow)
50-
that compute what data is moved and when. The results of these analyses
51-
are needed to do both borrow checking and region inference.
52-
- Using the move data, we can then compute the values of all the regions in the
53-
MIR.
54-
- (More details can be found in [the NLL section](./mir/regionck.html).)
55-
- Finally, the borrow checker itself runs, taking as input (a) the
56-
results of move analysis and (b) the regions computed by the region
57-
checker. This allows us to figure out which loans are still in scope
58-
at any particular point.
59-
47+
- Next, we perform a number of
48+
[dataflow analyses](./appendix/background.html#dataflow) that
49+
compute what data is moved and when.
50+
- We then do a [second type check](borrow_check/type_check.html) across the MIR:
51+
the purpose of this type check is to determine all of the constraints between
52+
different regions.
53+
- 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.
55+
- At this point, we can compute the "borrows in scope" at each point.
56+
- Finally, we do a second walk over the MIR, looking at the actions it
57+
does and reporting errors. For example, if we see a statement like
58+
`*a + 1`, then we would check that the variable `a` is initialized
59+
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.
62+
63+
[`replace_regions_in_mir`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/fn.replace_regions_in_mir.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Tracking moves and initialization
2+
3+
Part of the borrow checker's job is to track which variables are
4+
"initialized" at any given point in time -- this also requires
5+
figuring out where moves occur and tracking those.
6+
7+
## Initialization and moves
8+
9+
From a user's perspective, initialization -- giving a variable some
10+
value -- and moves -- transfering ownership to another place -- might
11+
seem like distinct topics. Indeed, our borrow checker error messages
12+
often talk about them differently. But **within the borrow checker**,
13+
they are not nearly as separate. Roughly speaking, the borrow checker
14+
tracks the set of "initialized places" at any point in time. Assigning
15+
to a previously uninitialized local variable adds it to that set;
16+
moving from a local variable removes it from that set.
17+
18+
Consider this example:
19+
20+
```rust
21+
fn foo() {
22+
let a: Vec<u32>;
23+
24+
// a is not initialized yet
25+
26+
a = vec![22];
27+
28+
// a is initialized here
29+
30+
std::mem::drop(a); // a is moved here
31+
32+
// a is no longer initialized here
33+
34+
let l = a.len(); //~ ERROR
35+
}
36+
```
37+
38+
Here you can see that `a` starts off as uninitialized; once it is
39+
assigned, it becomes initialized. But when `drop(a)` is called, it
40+
becomes uninitialized again.
41+
42+
## Subsections
43+
44+
To make it easier to peruse, this section is broken into a number of
45+
subsections:
46+
47+
- [Move paths](./moves_and_initialization/move_paths.html the
48+
*move path* concept that we use to track which local variables (or parts of
49+
local variables, in some cases) are initialized.
50+
- *Rest not yet written* =)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Move paths
2+
3+
In reality, it's not enough to track initialization at the granularity
4+
of local variables. Sometimes we need to track, e.g., individual fields:
5+
6+
```rust
7+
fn foo() {
8+
let a: (Vec<u32>, Vec<u32>) = (vec![22], vec![44]);
9+
10+
// a.0 and a.1 are both initialized
11+
12+
let b = a.0; // moves a.0
13+
14+
// a.0 is not initializd, but a.1 still is
15+
16+
let c = a.0; // ERROR
17+
let d = a.1; // OK
18+
}
19+
```
20+
21+
To handle this, we track initialization at the granularity of a **move
22+
path**. A [`MovePath`] represents some location that the user can
23+
initialize, move, etc. So e.g. there is a move-path representing the
24+
local variable `a`, and there is a move-path representing `a.0`. Move
25+
paths roughly correspond to the concept of a [`Place`] from MIR, but
26+
they are indexed in ways that enable us to do move analysis more
27+
efficiently.
28+
29+
[`MovePath`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/struct.MovePath.html
30+
[`Place`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/mir/enum.Place.html
31+
32+
## Move path indices
33+
34+
Although there is a [`MovePath`] data structure, they are never
35+
referenced directly. Instead, all the code passes around *indices* of
36+
type
37+
[`MovePathIndex`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/indexes/struct.MovePathIndex.html). If
38+
you need to get information about a move path, you use this index with
39+
the [`move_paths` field of the `MoveData`][move_paths]. For example,
40+
to convert a [`MovePathIndex`] `mpi` into a MIR [`Place`], you might
41+
access the [`MovePath::place`] field like so:
42+
43+
```rust
44+
move_data.move_paths[mpi].place
45+
```
46+
47+
[move_paths]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/struct.MoveData.html#structfield.move_paths
48+
[`MovePath::place`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/struct.MovePath.html#structfield.place
49+
50+
## Building move paths
51+
52+
One of the first things we do in the MIR borrow check is to construct
53+
the set of move paths. This is done as part of the
54+
[`MoveData::gather_moves`] function. This function uses a MIR visitor
55+
called [`Gatherer`] to walk the MIR and look at how each [`Place`]
56+
within is accessed. For each such [`Place`], it constructs a
57+
corresponding [`MovePathIndex`]. It also records when/where that
58+
particular move path is moved/initialized, but we'll get to that in a
59+
later section.
60+
61+
[`Gatherer`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/builder/struct.Gatherer.html
62+
[`MoveData::gather_moves`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/struct.MoveData.html#method.gather_moves
63+
64+
### Illegal move paths
65+
66+
We don't actually move-paths for **every** [`Place`] that gets used.
67+
In particular, if it is illegal to move from a [`Place`], then there
68+
is no need for a [`MovePathIndex`]. Some examples:
69+
70+
- You cannot move from a static variable, so we do not create a [`MovePathIndex`]
71+
for static variables.
72+
- You cannot move an individual element of an array, so if we have e.g. `foo: [String; 3]`,
73+
there would be no move-path for `foo[1]`.
74+
- You cannot move from inside of a borrowed reference, so if we have e.g. `foo: &String`,
75+
there would be no move-path for `*foo`.
76+
77+
These rules are enforced by the [`move_path_for`] function, which
78+
converts a [`Place`] into a [`MovePathIndex`] -- in error cases like
79+
those just discussed, the function returns an `Err`. This in turn
80+
means we don't have to bother tracking whether those places are
81+
initialized (which lowers overhead).
82+
83+
[`move_path_for`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/builder/struct.Gatherer.html#method.move_path_for
84+
85+
## Looking up a move-path
86+
87+
If you have a [`Place`] and you would like to convert it to a [`MovePathIndex`], you
88+
can do that using the [`MovePathLookup`] structure found in the [`rev_lookup`] field
89+
of [`MoveData`]. There are two different methods:
90+
91+
[`MovePathLookup`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/struct.MovePathLookup.html
92+
[`rev_lookup`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/struct.MoveData.html#structfield.rev_lookup
93+
94+
- [`find_local`], which takes a [`mir::Local`] representing a local
95+
variable. This is the easier method, because we **always** create a
96+
[`MovePathIndex`] for every local variable.
97+
- [`find`], which takes an arbitrary [`Place`]. This method is a bit
98+
more annoying to use, precisely because we don't have a
99+
[`MovePathIndex`] for **every** [`Place`] (as we just discussed in
100+
the "illegal move paths" section). Therefore, [`find`] returns a
101+
[`LookupResult`] indicating the closest path it was able to find
102+
that exists (e.g., for `foo[1]`, it might return just the path for
103+
`foo`).
104+
105+
[`find`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/struct.MovePathLookup.html#method.find
106+
[`find_local`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/struct.MovePathLookup.html#method.find_local
107+
[`mir::Local`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/mir/struct.Local.html
108+
[`LookupResult`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/move_paths/enum.LookupResult.html
109+
110+
## Cross-references
111+
112+
As we noted above, move-paths are stored in a big vector and
113+
referenced via their [`MovePathIndex`]. However, within this vector,
114+
they are also structured into a tree. So for example if you have the
115+
[`MovePathIndex`] for `a.b.c`, you can go to its parent move-path
116+
`a.b`. You can also iterate over all children paths: so, from `a.b`,
117+
you might iterate to find the path `a.b.c` (here you are iterating
118+
just over the paths that the user **actually referenced**, not all
119+
**possible** paths the user could have done). These references are
120+
used for example in the [`has_any_child_of`] function, which checks
121+
whether the dataflow results contain a value for the given move-path
122+
(e.g., `a.b`) or any child of that move-path (e.g., `a.b.c`).
123+
124+
[`Place`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/mir/enum.Place.html
125+
[`has_any_child_of`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/dataflow/at_location/struct.FlowAtLocation.html#method.has_any_child_of
126+

src/borrow_check/region_inference.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
# MIR-based region checking (NLL)
1+
# Region inference (NLL)
22

33
The MIR-based region checking code is located in
44
[the `rustc_mir::borrow_check::nll` module][nll]. (NLL, of course,
55
stands for "non-lexical lifetimes", a term that will hopefully be
66
deprecated once they become the standard kind of lifetime.)
77

8-
[nll]: https://github.com/rust-lang/rust/tree/master/src/librustc_mir/borrow_check/nll
8+
[nll]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/index.html
99

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

src/borrow_check/type_check.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# The MIR type-check
2+
3+
A key component of the borrow check is the
4+
[MIR type-check](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/type_check/index.html).
5+
This check walks the MIR and does a complete "type check" -- the same
6+
kind you might find in any other language. In the process of doing
7+
this type-check, we also uncover the region constraints that apply to
8+
the program.
9+
10+
11+

0 commit comments

Comments
 (0)