| # MIR borrow check |
| |
| The borrow check is Rust's "secret sauce" – it is tasked with |
| enforcing a number of properties: |
| |
| - That all variables are initialized before they are used. |
| - That you can't move the same value twice. |
| - That you can't move a value while it is borrowed. |
| - That you can't access a place while it is mutably borrowed (except through |
| the reference). |
| - That you can't mutate a place while it is immutably borrowed. |
| - etc |
| |
| The borrow checker operates on the MIR. An older implementation operated on the |
| HIR. Doing borrow checking on MIR has several advantages: |
| |
| - The MIR is *far* less complex than the HIR; the radical desugaring |
| helps prevent bugs in the borrow checker. (If you're curious, you |
| can see |
| [a list of bugs that the MIR-based borrow checker fixes here][47366].) |
| - Even more importantly, using the MIR enables ["non-lexical lifetimes"][nll], |
| which are regions derived from the control-flow graph. |
| |
| [47366]: https://github.com/rust-lang/rust/issues/47366 |
| [nll]: https://rust-lang.github.io/rfcs/2094-nll.html |
| |
| ### Major phases of the borrow checker |
| |
| The borrow checker source is found in |
| [the `rustc_borrowck` crate][b_c]. The main entry point is |
| the [`mir_borrowck`] query. |
| |
| [b_c]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_borrowck/index.html |
| [`mir_borrowck`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_borrowck/fn.mir_borrowck.html |
| |
| - We first create a **local copy** of the MIR. In the coming steps, |
| we will modify this copy in place to modify the types and things to |
| include references to the new regions that we are computing. |
| - We then invoke [`replace_regions_in_mir`] to modify our local MIR. |
| Among other things, this function will replace all of the [regions](./appendix/glossary.md#region) |
| in the MIR with fresh [inference variables](./appendix/glossary.md#inf-var). |
| - Next, we perform a number of |
| [dataflow analyses](./appendix/background.md#dataflow) that |
| compute what data is moved and when. |
| - We then do a [second type check](borrow_check/type_check.md) across the MIR: |
| 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.md), which computes |
| 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. |
| |
| [`replace_regions_in_mir`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_borrowck/nll/fn.replace_regions_in_mir.html |