diff --git a/src/borrow_check.md b/src/borrow_check.md index 40858b1b4..b3eeaa387 100644 --- a/src/borrow_check.md +++ b/src/borrow_check.md @@ -51,13 +51,14 @@ the [`mir_borrowck`] query. the purpose of this type check is to determine all of the constraints between different regions. - Next, we do [region inference](borrow_check/region_inference.html), which computes - the values of each region — basically, points in the control-flow graph. + the values of each region — basically, the points in the control-flow graph where + each lifetime must be valid according to the constraints we collected. - At this point, we can compute the "borrows in scope" at each point. - Finally, we do a second walk over the MIR, looking at the actions it does and reporting errors. For example, if we see a statement like `*a + 1`, then we would check that the variable `a` is initialized and that it is not mutably borrowed, as either of those would - require an error to be reported. - - Doing this check requires the results of all the previous analyses. + require an error to be reported. Doing this check requires the results of all + the previous analyses. [`replace_regions_in_mir`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/fn.replace_regions_in_mir.html diff --git a/src/borrow_check/region_inference.md b/src/borrow_check/region_inference.md index 95c2bc804..7fe50b870 100644 --- a/src/borrow_check/region_inference.md +++ b/src/borrow_check/region_inference.md @@ -9,7 +9,7 @@ deprecated once they become the standard kind of lifetime.) The MIR-based region analysis consists of two major functions: -- `replace_regions_in_mir`, invoked first, has two jobs: +- [`replace_regions_in_mir`], invoked first, has two jobs: - First, it finds the set of regions that appear within the signature of the function (e.g., `'a` in `fn foo<'a>(&'a u32) { ... }`). These are called the "universal" or "free" regions – in @@ -21,49 +21,67 @@ The MIR-based region analysis consists of two major functions: not of much interest. The intention is that – eventually – they will be "erased regions" (i.e., no information at all), since we won't be doing lexical region inference at all. -- `compute_regions`, invoked second: this is given as argument the +- [`compute_regions`], invoked second: this is given as argument the results of move analysis. It has the job of computing values for all the inference variables that `replace_regions_in_mir` introduced. - - To do that, it first runs the [MIR type checker](#mirtypeck). This + - To do that, it first runs the [MIR type checker]. This is basically a normal type-checker but specialized to MIR, which - is much simpler than full Rust of course. Running the MIR type + is much simpler than full Rust, of course. Running the MIR type checker will however create **outlives constraints** between region variables (e.g., that one variable must outlive another one) to reflect the subtyping relationships that arise. - It also adds **liveness constraints** that arise from where variables are used. - - More details to come, though the [NLL RFC] also includes fairly thorough - (and hopefully readable) coverage. + - After this, we create a [`RegionInferenceContext`] with the constraints we + have computed and the inference variables we introduced and use the + [`solve`] method to infer values for all region inference varaibles. + - The [NLL RFC] also includes fairly thorough (and hopefully readable) + coverage. [fvb]: ../appendix/background.html#free-vs-bound +[`replace_regions_in_mir`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/fn.replace_regions_in_mir.html +[`compute_regions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/fn.compute_regions.html +[`RegionInferenceContext`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html +[`solve`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.solve [NLL RFC]: http://rust-lang.github.io/rfcs/2094-nll.html +[MIR type checker]: ./type_check.md ## Universal regions -*to be written* – explain the `UniversalRegions` type +The [`UnversalRegions`] type represents a collection of _universal_ regions +corresponding to some MIR `DefId`. It is constructed in +[`replace_regions_in_mir`] when we replace all regions with fresh inference +variables. [`UniversalRegions`] contains indices for all the free regions in +the given MIR along with any relationships that are _known_ to hold between +them (e.g. implied bounds, where clauses, etc.). -## Region variables and constraints +For example, given the MIR for the following function: -*to be written* – describe the `RegionInferenceContext` and -the role of `liveness_constraints` vs other `constraints`, plus - -## Closures +```rust +fn foo<'a>(x: &'a u32) { + // ... +} +``` -*to be written* +we would create a universal region for `'a` and one for `'static`. There may +also be some complications for handling closures, but we will ignore those for +the moment. - +TODO: write about _how_ these regions are computed. -## The MIR type-check +[`UniversalRegions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/universal_regions/struct.UniversalRegions.html -## Representing the "values" of a region variable +## Region variables -The value of a region can be thought of as a **set**; we call the -domain of this set a `RegionElement`. In the code, the value for all -regions is maintained in -[the `rustc_mir::borrow_check::nll::region_infer` module][ri]. For -each region we maintain a set storing what elements are present in its -value (to make this efficient, we give each kind of element an index, -the `RegionElementIndex`, and use sparse bitsets). +The value of a region can be thought of as a **set**. This set contains all +points in the MIR where the region is valid along with any regions that are +outlived by this region (e.g. if `'a: 'b`, then `end('b)` is in the set for +`'a`); we call the domain of this set a `RegionElement`. In the code, the value +for all regions is maintained in [the +`rustc_mir::borrow_check::nll::region_infer` module][ri]. For each region we +maintain a set storing what elements are present in its value (to make this +efficient, we give each kind of element an index, the `RegionElementIndex`, and +use sparse bitsets). [ri]: https://github.com/rust-lang/rust/tree/master/src/librustc_mir/borrow_check/nll/region_infer/ @@ -83,12 +101,148 @@ The kinds of region elements are as follows: for details on placeholders, see the section [placeholders and universes](#placeholder). -## Causal tracking +## Constraints + +Before we can infer the value of regions, we need to collect constraints on the +regions. There are two primary types of constraints. + +1. Outlives constraints. These are constraints that one region outlives another + (e.g. `'a: 'b`). Outlives constraints are generated by the [MIR type + checker]. +2. Liveness constraints. Each region needs to be live at points where it can be + used. These constraints are collected by [`generate_constraints`]. + +[`generate_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/constraint_generation/fn.generate_constraints.html + +## Inference Overview + +So how do we compute the contents of a region? This process is called _region +inference_. The high-level idea is pretty simple, but there are some details we +need to take care of. + +Here is the high-level idea: we start off each region with the MIR locations we +know must be in it from the liveness constraints. From there, we use all of the +outlives constraints computed from the type checker to _propagate_ the +constraints: for each region `'a`, if `'a: 'b`, then we add all elements of +`'b` to `'a`, including `end('b)`. This all happens in +[`propagate_constraints`]. + +Then, we will check for errors. We first check that type tests are satisfied by +calling [`check_type_tests`]. This checks constraints like `T: 'a`. Second, we +check that universal regions are not "too big". This is done by calling +[`check_universal_regions`]. This checks that for each region `'a` if `'a` +contains the element `end('b)`, then we must already know that `'a: 'b` holds +(e.g. from a where clause). If we don't already know this, that is an error... +well, almost. There is some special handling for closures that we will discuss +later. + +### Example + +Consider the following example: -*to be written* – describe how we can extend the values of a variable - with causal tracking etc +```rust,ignore +fn foo<'a, 'b>(x: &'a usize) -> &'b usize { + x +} +``` + +Clearly, this should not compile because we don't know if `'a` outlives `'b` +(if it doesn't then the return value could be a dangling reference). - +Let's back up a bit. We need to introduce some free inference variables (as is +done in [`replace_regions_in_mir`]). This example doesn't use the exact regions +produced, but it (hopefully) is enough to get the idea across. + +```rust,ignore +fn foo<'a, 'b>(x: &'a /* '#1 */ usize) -> &'b /* '#3 */ usize { + x // '#2, location L1 +} +``` + +Some notation: `'#1`, `'#3`, and `'#2` represent the universal regions for the +argument, return value, and the expression `x`, respectively. Additionally, I +will call the location of the expression `x` `L1`. + +So now we can use the liveness constraints to get the following starting points: + +Region | Contents +--------|---------- +'#1 | +'#2 | `L1` +'#3 | `L1` + +Now we use the outlives constraints to expand each region. Specifically, we +know that `'#2: '#3` ... + +Region | Contents +--------|---------- +'#1 | `L1` +'#2 | `L1, end('#3) // add contents of '#3 and end('#3)` +'#3 | `L1` + +... and `'#1: '#2`, so ... + +Region | Contents +--------|---------- +'#1 | `L1, end('#2), end('#3) // add contents of '#2 and end('#2)` +'#2 | `L1, end('#3)` +'#3 | `L1` + +Now, we need to check that no regions were too big (we don't have any type +tests to check in this case). Notice that `'#1` now contains `end('#3)`, but +we have no `where` clause or implied bound to say that `'a: 'b`... that's an +error! + +### Some details + +The [`RegionInferenceContext`] type contains all of the information needed to +do inference, including the universal regions from [`replace_regions_in_mir`] and +the constraints computed for each region. It is constructed just after we +compute the liveness constraints. + +Here are some of the fields of the struct: + +- [`constraints`]: contains all the outlives constraints. +- [`liveness_constraints`]: contains all the liveness constraints. +- [`universal_regions`]: contains the `UniversalRegions` returned by + [`replace_regions_in_mir`]. +- [`universal_region_relations`]: contains relations known to be true about + universal regions. For example, if we have a where clause that `'a: 'b`, that + relation is assumed to be true while borrow checking the implementation (it + is checked at the caller), so `universal_region_relations` would contain `'a: + 'b`. +- [`type_tests`]: contains some constraints on types that we must check after + inference (e.g. `T: 'a`). +- [`closure_bounds_mapping`]: used for propagating region constraints from + closures back out to the creater of the closure. + +[`constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.constraints +[`liveness_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.liveness_constraints +[`universal_regions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.universal_regions +[`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 +[`type_tests`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#structfield.type_tests +[`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 + +TODO: should we discuss any of the others fields? What about the SCCs? + +Ok, now that we have constructed a `RegionInferenceContext`, we can do +inference. This is done by calling the [`solve`] method on the context. This +is where we call [`propagate_constraints`] and then check the resulting type +tests and universal regions, as discussed above. + +[`propagate_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/borrow_check/nll/region_infer/struct.RegionInferenceContext.html#method.propagate_constraints +[`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 +[`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 + +## Closures + +When we are checking the type tests and universal regions, we may come across a +constraint that we can't prove yet if we are in a closure body! However, the +necessary constraints may actually hold (we just don't know it yet). Thus, if +we are inside a closure, we just collect all the constraints we can't prove yet +and return them. Later, when we are borrow check the MIR node that created the +closure, we can also check that these constraints hold. At that time, if we +can't prove they hold, we report an error. ## Placeholders and universes @@ -534,3 +688,6 @@ Now constraint propagation is done, but when we check the outlives relationships, we find that `V2` includes this new element `placeholder(1)`, so we report an error. +## Borrow Checker Errors + +TODO: we should discuss how to generate errors from the results of these analyses.