Unsafety checking Certain expressions in Rust can violate memory safety and as such need to be inside an unsafe block or function. The compiler will also warn if an unsafe block is used without any corresponding unsafe operations. Overview...
Dataflow Analysis If you work on the MIR, you will frequently come across various flavors of dataflow analysis. rustc uses dataflow to find uninitialized variables, determine what variables are live across a generator yield statement, and c...
Drop elaboration Dynamic drops According to the reference: When an initialized variable or temporary goes out of scope, its destructor is run, or it is dropped. Assignment also runs the destructor of its left-hand operand, if it's initializ...
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...
Tracking moves and initialization Part of the borrow checker's job is to track which variables are "initialized" at any given point in time -- this also requires figuring out where moves occur and tracking those. Initialization and moves Fr...
Move paths In reality, it's not enough to track initialization at the granularity of local variables. Rust also allows us to do moves and initialization at the field granularity: fn foo() { let a: (Vec<u32>, Vec<u32>) = (vec![22], vec![44])...
The MIR type-check A key component of the borrow check is the MIR type-check. This check walks the MIR and does a complete "type check" -- the same kind you might find in any other language. In the process of doing this type-check, we also...
Drop Check We generally require the type of locals to be well-formed whenever the local is used. This includes proving the where-bounds of the local and also requires all regions used by it to be live. The only exception to this is when imp...
Region inference (NLL) The MIR-based region checking code is located in the rustc_mir::borrow_check module. The MIR-based region analysis consists of two major functions: • replace_regions_in_mir, invoked first, has two jobs: • First, it fi...
Constraint propagation The main work of the region inference is constraint propagation, which is done in the propagate_constraints function. There are three sorts of constraints that are used in NLL, and we'll explain how propagate_constrai...
Universal regions "Universal regions" is the name that the code uses to refer to "named lifetimes" -- e.g., lifetime parameters and 'static. The name derives from the fact that such lifetimes are "universally quantified" (i.e., we must make...
Member constraints A member constraint 'm member of ['c_1..'c_N] expresses that the region 'm must be equal to some choice regions 'c_i (for some i). These constraints cannot be expressed by users, but they arise from impl Trait due to its...
Placeholders and universes From time to time we have to reason about regions that we can't concretely know. For example, consider this program: // A function that needs a static reference fn foo(x: &'static u32) { } fn bar(f: for<'a> fn(&'a...
Propagating closure constraints 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 do...
Reporting region errors TODO: we should discuss how to generate errors from the results of these analyses.
Two-phase borrows Two-phase borrows are a more permissive version of mutable borrows that allow nested method calls such as vec.push(vec.len()). Such borrows first act as shared borrows in a "reservation" phase and can later be "activated"...
Closure Capture Inference This section describes how rustc handles closures. Closures in Rust are effectively "desugared" into structs that contain the values they use (or references to the values they use) from their creator's stack frame....
Async closures/"coroutine-closures" Please read RFC 3668 to understand the general motivation of the feature. This is a very technical and somewhat "vertical" chapter; ideally we'd split this and sprinkle it across all the relevant chapters...
From MIR to binaries All of the preceding chapters of this guide have one thing in common: we never generated any executable machine code at all! With this chapter, all of that changes. So far, we've shown how the compiler can take raw sour...
MIR optimizations MIR optimizations are optimizations run on the MIR to produce better MIR before codegen. This is important for two reasons: first, it makes the final generated executable code better, and second, it means that LLVM has les...