diff --git a/text/2094-nll.md b/text/2094-nll.md new file mode 100644 index 00000000000..7ec8b36c82d --- /dev/null +++ b/text/2094-nll.md @@ -0,0 +1,2190 @@ +- Feature Name: (fill me in with a unique ident, my_awesome_feature) +- Start Date: 2017-08-02 +- RFC PR: https://github.com/rust-lang/rfcs/pull/2094 +- Rust Issue: https://github.com/rust-lang/rust/issues/44928 + +# Summary +[summary]: #summary + +Extend Rust's borrow system to support **non-lexical lifetimes** -- +these are lifetimes that are based on the control-flow graph, rather +than lexical scopes. The RFC describes in detail how to infer these +new, more flexible regions, and also describes how to adjust our error +messages. The RFC also describes a few other extensions to the borrow +checker, the total effect of which is to eliminate many common cases +where small, function-local code modifications would be required to pass the +borrow check. (The appendix describes some of the remaining +borrow-checker limitations that are not addressed by this RFC.) + +# Motivation +[motivation]: #motivation + +## What is a lifetime? + +The basic idea of the borrow checker is that values may not be mutated +or moved while they are borrowed, but how do we know whether a value +is borrowed? The idea is quite simple: whenever you create a borrow, +the compiler assigns the resulting reference a **lifetime**. This +lifetime corresponds to the span of the code where the reference may +be used. The compiler will infer this lifetime to be the smallest +lifetime that it can have that still encompasses all the uses of the +reference. + +Note that Rust uses the term lifetime in a very particular way. In +everyday speech, the word lifetime can be used in two distinct -- but +similar -- ways: + +1. The lifetime of a **reference**, corresponding to the span of time in + which that reference is **used**. +2. The lifetime of a **value**, corresponding to the span of time + before that value gets **freed** (or, put another way, before the + destructor for the value runs). + +This second span of time, which describes how long a value is valid, +is very important. To distinguish the two, we refer to that +second span of time as the value's **scope**. Naturally, lifetimes and +scopes are linked to one another. Specifically, if you make a +reference to a value, the lifetime of that reference cannot outlive +the scope of that value. Otherwise, your reference would be pointing +into freed memory. + +To better see the distinction between lifetime and scope, let's +consider a simple example. In this example, the vector `data` is +borrowed (mutably) and the resulting reference is passed to a function +`capitalize`. Since `capitalize` does not return the reference back, +the *lifetime* of this borrow will be confined to just that call. The +*scope* of data, in contrast, is much larger, and corresponds to a +suffix of the fn body, stretching from the `let` until the end of the +enclosing scope. + +```rust +fn foo() { + let mut data = vec!['a', 'b', 'c']; // --+ 'scope + capitalize(&mut data[..]); // | +// ^~~~~~~~~~~~~~~~~~~~~~~~~ 'lifetime // | + data.push('d'); // | + data.push('e'); // | + data.push('f'); // | +} // <---------------------------------------+ + +fn capitalize(data: &mut [char]) { + // do something +} +``` + +This example also demonstrates something else. Lifetimes in Rust today +are quite a bit more flexible than scopes (if not as flexible as we +might like, hence this RFC): + +- A scope generally corresponds to some block (or, more specifically, + a *suffix* of a block that stretches from the `let` until the end of + the enclosing block) \[[1](#temporaries)\]. +- A lifetime, in contrast, can also span an individual expression, as + this example demonstrates. The lifetime of the borrow in the example + is confined to just the call to `capitalize`, and doesn't extend + into the rest of the block. This is why the calls to `data.push` + that come below are legal. + +So long as a reference is only used within one statement, today's +lifetimes are typically adequate. Problems arise however when you have +a reference that spans multiple statements. In that case, the compiler +requires the lifetime to be the innermost expression (which is often a +block) that encloses both statements, and that is typically much +bigger than is really necessary or desired. Let's look at some example +problem cases. Later on, we'll see how non-lexical lifetimes fix these +cases. + +## Problem case #1: references assigned into a variable + +One common problem case is when a reference is assigned into a +variable. Consider this trivial variation of the previous example, +where the `&mut data[..]` slice is not passed directly to +`capitalize`, but is instead stored into a local variable: + +```rust +fn bar() { + let mut data = vec!['a', 'b', 'c']; + let slice = &mut data[..]; // <-+ 'lifetime + capitalize(slice); // | + data.push('d'); // ERROR! // | + data.push('e'); // ERROR! // | + data.push('f'); // ERROR! // | +} // <------------------------------+ +``` + +The way that the compiler currently works, assigning a reference into +a variable means that its lifetime must be as large as the entire +scope of that variable. In this case, that means the lifetime is now +extended all the way until the end of the block. This in turn means +that the calls to `data.push` are now in error, because they occur +during the lifetime of `slice`. It's logical, but it's annoying. + +In this particular case, you could resolve the problem by putting +`slice` into its own block: + +```rust +fn bar() { + let mut data = vec!['a', 'b', 'c']; + { + let slice = &mut data[..]; // <-+ 'lifetime + capitalize(slice); // | + } // <------------------------------+ + data.push('d'); // OK + data.push('e'); // OK + data.push('f'); // OK +} +``` + +Since we introduced a new block, the scope of `slice` is now smaller, +and hence the resulting lifetime is smaller. Introducing a +block like this is kind of artificial and also not an entirely obvious +solution. + +## Problem case #2: conditional control flow + +Another common problem case is when references are used in only one +given match arm (or, more generally, one control-flow path). This most +commonly arises around maps. Consider this function, which, given some +`key`, processes the value found in `map[key]` if it exists, or else +inserts a default value: + +```rust +fn process_or_default() { + let mut map = ...; + let key = ...; + match map.get_mut(&key) { // -------------+ 'lifetime + Some(value) => process(value), // | + None => { // | + map.insert(key, V::default()); // | + // ^~~~~~ ERROR. // | + } // | + } // <------------------------------------+ +} +``` + +This code will not compile today. The reason is that the `map` is +borrowed as part of the call to `get_mut`, and that borrow must +encompass not only the call to `get_mut`, but also the `Some` branch +of the match. The innermost expression that encloses both of these +expressions is the match itself (as depicted above), and hence the +borrow is considered to extend until the end of the +match. Unfortunately, the match encloses not only the `Some` branch, +but also the `None` branch, and hence when we go to insert into the +map in the `None` branch, we get an error that the `map` is still +borrowed. + +This *particular* example is relatively easy to workaround. In many cases, +one can move the code for `None` out from the `match` like so: + +```rust +fn process_or_default1() { + let mut map = ...; + let key = ...; + match map.get_mut(&key) { // -------------+ 'lifetime + Some(value) => { // | + process(value); // | + return; // | + } // | + None => { // | + } // | + } // <------------------------------------+ + map.insert(key, V::default()); +} +``` + +When the code is adjusted this way, the call to `map.insert` is not +part of the match, and hence it is not part of the borrow. While this +works, it is unfortunate to require these sorts of +manipulations, just as it was when we introduced an artificial block +in the previous example. + +## Problem case #3: conditional control flow across functions + +While we were able to work around problem case #2 in a relatively +simple, if irritating, fashion, there are other variations of +conditional control flow that cannot be so easily resolved. This is +particularly true when you are returning a reference out of a +function. Consider the following function, which returns the value for +a key if it exists, and inserts a new value otherwise (for the +purposes of this section, assume that the `entry` API for maps does +not exist): + +```rust +fn get_default<'r,K,V:Default>(map: &'r mut HashMap, + key: K) + -> &'r mut V { + match map.get_mut(&key) { // -------------+ 'r + Some(value) => value, // | + None => { // | + map.insert(key, V::default()); // | + // ^~~~~~ ERROR // | + map.get_mut(&key).unwrap() // | + } // | + } // | +} // v +``` + +At first glance, this code appears quite similar to the code we saw +before, and indeed, just as before, it will not compile. In fact, +the lifetimes at play are quite different. The reason is that, in the +`Some` branch, the value is being **returned out** to the caller. +Since `value` is a reference into the map, this implies that the `map` +will remain borrowed **until some point in the caller** (the point +`'r`, to be exact). To get a better intuition for what this lifetime +parameter `'r` represents, consider some hypothetical caller of +`get_default`: the lifetime `'r` then represents the span of code in +which that caller will use the resulting reference: + +```rust +fn caller() { + let mut map = HashMap::new(); + ... + { + let v = get_default(&mut map, key); // -+ 'r + // +-- get_default() -----------+ // | + // | match map.get_mut(&key) { | // | + // | Some(value) => value, | // | + // | None => { | // | + // | .. | // | + // | } | // | + // +----------------------------+ // | + process(v); // | + } // <--------------------------------------+ + ... +} +``` + +If we attempt the same workaround for this case that we tried +in the previous example, we will find that it does not work: + +```rust +fn get_default1<'r,K,V:Default>(map: &'r mut HashMap, + key: K) + -> &'r mut V { + match map.get_mut(&key) { // -------------+ 'r + Some(value) => return value, // | + None => { } // | + } // | + map.insert(key, V::default()); // | + // ^~~~~~ ERROR (still) | + map.get_mut(&key).unwrap() // | +} // v +``` + +Whereas before the lifetime of `value` was confined to the match, this +new lifetime extends out into the caller, and therefore the borrow +does not end just because we exited the match. Hence it is still in +scope when we attempt to call `insert` after the match. + +The workaround for this problem is a bit more involved. It relies on +the fact that the borrow checker uses the precise control-flow of the +function to determine which borrows are in scope. + +```rust +fn get_default2<'r,K,V:Default>(map: &'r mut HashMap, + key: K) + -> &'r mut V { + if map.contains(&key) { + // ^~~~~~~~~~~~~~~~~~ 'n + return match map.get_mut(&key) { // + 'r + Some(value) => value, // | + None => unreachable!() // | + }; // v + } + + // At this point, `map.get_mut` was never + // called! (As opposed to having been called, + // but its result no longer being in use.) + map.insert(key, V::default()); // OK now. + map.get_mut(&key).unwrap() +} +``` + +What has changed here is that we moved the call to `map.get_mut` +inside of an `if`, and we have set things up so that the if body +unconditionally returns. What this means is that a borrow begins at +the point of `get_mut`, and that borrow lasts until the point `'r` in +the caller, but the borrow checker can see that this borrow *will not +have even started* outside of the `if`. It does not consider the +borrow in scope at the point where we call `map.insert`. + +This workaround is more troublesome than the others, because the +resulting code is actually less efficient at runtime, since it must do +multiple lookups. + +It's worth noting that Rust's hashmaps include an `entry` API that +one could use to implement this function today. The resulting code is +both nicer to read and more efficient even than the original version, +since it avoids extra lookups on the "not present" path as well: + +```rust +fn get_default3<'r,K,V:Default>(map: &'r mut HashMap, + key: K) + -> &'r mut V { + map.entry(key) + .or_insert_with(|| V::default()) +} +``` + +Regardless, the problem exists for other data structures besides +`HashMap`, so it would be nice if the original code passed the borrow +checker, even if in practice using the `entry` API would be +preferable. (Interestingly, the limitation of the borrow checker here +was one of the motivations for developing the `entry` API in the first +place!) + +## Problem case #4: mutating `&mut` references + +The current borrow checker forbids reassigning an `&mut` variable `x` +when the referent (`*x`) has been borrowed. This most commonly arises +when writing a loop that progressively "walks down" a data structure. +Consider this function, which converts a linked list `&mut List` +into a `Vec<&mut T>`: + +```rust +struct List { + value: T, + next: Option>>, +} + +fn to_refs(mut list: &mut List) -> Vec<&mut T> { + let mut result = vec![]; + loop { + result.push(&mut list.value); + if let Some(n) = list.next.as_mut() { + list = &mut n; + } else { + return result; + } + } +} +``` + +If we attempt to compile this, we get an error (actually, we get +multiple errors): + +``` +error[E0506]: cannot assign to `list` because it is borrowed + --> /Users/nmatsakis/tmp/x.rs:11:13 + | +9 | result.push(&mut list.value); + | ---------- borrow of `list` occurs here +10 | if let Some(n) = list.next.as_mut() { +11 | list = n; + | ^^^^^^^^ assignment to borrowed `list` occurs here +``` + +Specifically, what's gone wrong is that we borrowed `list.value` (or, +more explicitly, `(*list).value`). The current borrow checker enforces +the rule that when you borrow a path, you cannot assign to that path +or any prefix of that path. In this case, that means you cannot assign to any +of the following: + +- `(*list).value` +- `*list` +- `list` + +As a result, the `list = n` assignment is forbidden. These rules make +sense in some cases (for example, if `list` were of type `List`, +and not `&mut List`, then overwriting `list` would also overwrite +`list.value`), but not in the case where we cross a mutable reference. + +As described in [Issue #10520][10520], there exist various workarounds +for this problem. One trick is to move the `&mut` reference into a +temporary variable that you won't have to modify: + +```rust +fn to_refs(mut list: &mut List) -> Vec<&mut T> { + let mut result = vec![]; + loop { + let list1 = list; + result.push(&mut list1.value); + if let Some(n) = list1.next.as_mut() { + list = &mut n; + } else { + return result; + } + } +} +``` + +When you frame the program this way, the borrow checker sees that +`(*list1).value` is borrowed (not `list`). This does not prevent us +from later assigning to `list`. + +Clearly this workaround is annoying. The problem here, it turns out, +is not specific to non-lexical lifetimes per se. Rather, it is that +the rules which the borrow checker enforces when a path is borrowed +are too strict and do not account for the indirection inherent in a +borrowed reference. This RFC proposes a tweak to address that. + +## The rough outline of our solution + +This RFC proposes a more flexible model for lifetimes. Whereas +previously lifetimes were based on the abstract syntax tree, we now +propose lifetimes that are defined via the control-flow graph. More +specifically, lifetimes will be derived based on the [MIR][MIR-details] +used internally in the compiler. + +[MIR-details]: https://blog.rust-lang.org/2016/04/19/MIR.html + +Intuitively, in the new proposal, the lifetime of a reference lasts +only for those portions of the function in which the reference may +later be used (where the reference is **live**, in compiler +speak). This can range from a few sequential statements (as in problem +case #1) to something more complex, such as covering one arm in a +match but not the others (problem case #2). + +However, in order to sucessfully type the full range of examples that +we would like, we have to go a bit further than just changing +lifetimes to a portion of the control-flow graph. **We also have to +take location into account when doing subtyping checks**. This is in +contrast to how the compiler works today, where subtyping relations +are "absolute". That is, in the current compiler, the type `&'a ()` is +a subtype of the type `&'b ()` whenever `'a` outlives `'b` (`'a: 'b`), +which means that `'a` corresponds to a bigger portion of the function. +Under this proposal, subtyping can instead be established **at a +particular point P**. In that case, the lifetime `'a` must only +outlive those portions of `'b` that are reachable from P. + +The ideas in this RFC have been implemented in +[prototype form][proto]. This prototype includes a simplified +control-flow graph that allows one to create the various kinds of +region constraints that can arise and implements the region inference +algorithm which then solves those constraints. + +[proto]: https://github.com/nikomatsakis/nll + +# Detailed design +[design]: #detailed-design + +## Layering the design + +We describe the design in "layers": + +1. Initially, we will describe a basic design focused on control-flow + within one function. +2. Next, we extend the control-flow graph to better handle infinite loops. +3. Next, we extend the design to handle dropck, and specifically the + `#[may_dangle]` attribute introduced by RFC 1327. +4. Next, we will extend the design to consider named lifetime parameters, + like those in problem case 3. +5. Finally, we give a brief description of the borrow checker. + +## Layer 0: Definitions + +Before we can describe the design, we have to define the terms that we +will be using. The RFC is defined in terms of a simplified version of +MIR, eliding various details that don't introduce fundamental +complexity. + +**Lvalues**. A MIR "lvalue" is a path that leads to a memory location. +The full MIR Lvalues are defined [via a Rust enum][lvaluecode] and +contain a number of knobs, most of which are not relevant for this RFC. +We will present a simplified form of lvalues for now: + +``` +LV = x // local variable + | LV.f // field access + | *LV // deref +``` + +The precedence of `*` is low, so `*a.b.c` will deref `a.b.c`; to deref +just `a`, one would write `(*a).b.c`. + +**Prefixes.** We say that the prefixes of an lvalue are all the +lvalues you get by stripping away fields and derefs. The prefixes +of `*a.b` would be `*a.b`, `a.b`, and `a`. + +[lvaluecode]: https://github.com/rust-lang/rust/blob/bf0a9e0b4d3a4dd09717960840798e2933ec7568/src/librustc/mir/mod.rs#L839-L851 + +**Control-flow graph.** MIR is organized into a +[control-flow graph][cfg] rather than an abstract syntax tree. It is +created in the compiler by transforming the "HIR" (high-level IR). The +MIR CFG consists of a set of [basic blocks][bbdata]. Each basic block +has a series of [statements][stmt] and a +[terminator][term]. Statements that concern us in this RFC fall into +three categories: + +- assignments like `x = y`; the RHS of such an assignment is called an + [rvalue][]. There are no compound rvalues, and hence each statement + is a discrete action that executes instantaneously. For example, the + Rust expression `a = b + c + d` would be compiled into two MIR + instructions, like `tmp0 = b + c; a = tmp0 + d;`. +- `drop(lvalue)` deallocates an lvalue, if there is a value in it; in the + limit, this requires runtime checks (a pass in mir, called elaborate drops, + performs this transformation). +- `StorageDead(x)` deallocates the stack storage for `x`. These are used by LLVM to allow + stack-allocated values to use the same stack slot (if their live storage ranges are disjoint). + [Ralf Jung's recent blog post has more details.][rjung-sd] + +[rjung-sd]: https://www.ralfj.de/blog/2017/06/06/MIR-semantics.html +[rvalue]: https://github.com/rust-lang/rust/blob/bf0a9e0b4d3a4dd09717960840798e2933ec7568/src/librustc/mir/mod.rs#L1037-L1071 +[bbdata]: https://github.com/rust-lang/rust/blob/bf0a9e0b4d3a4dd09717960840798e2933ec7568/src/librustc/mir/mod.rs#L443-L463 +[stmt]: https://github.com/rust-lang/rust/blob/bf0a9e0b4d3a4dd09717960840798e2933ec7568/src/librustc/mir/mod.rs#L774-L814 +[term]: https://github.com/rust-lang/rust/blob/bf0a9e0b4d3a4dd09717960840798e2933ec7568/src/librustc/mir/mod.rs#L465-L552 +[cfg]: https://en.wikipedia.org/wiki/Control_flow_graph + +## Layer 1: Control-flow within a function + +### Running Example + +We will explain the design with reference to a running example, called +**Example 4**. After presenting the design, we will apply it to the three +problem cases, as well as a number of other interesting examples. + +```rust +let mut foo: T = ...; +let mut bar: T = ...; +let p: &T; + +p = &foo; +// (0) +if condition { + print(*p); + // (1) + p = &bar; + // (2) +} +// (3) +print(*p); +// (4) +``` + +The key point of this example is that the variable `foo` should only +be considered borrowed at points 0 and 3, but not point 1. `bar`, +in contrast, should be considered borrowed at points 2 and 3. Neither +of them need to be considered borrowed at point 4, as the reference `p` +is not used there. + +We can convert this example into the control-flow graph that follows. +Recall that a control-flow graph in MIR consists of basic blocks +containing a list of discrete statements and a trailing terminator: + +``` +// let mut foo: i32; +// let mut bar: i32; +// let p: &i32; + +A +[ p = &foo ] +[ if condition ] ----\ (true) + | | + | B v + | [ print(*p) ] + | [ ... ] + | [ p = &bar ] + | [ ... ] + | [ goto C ] + | | + +-------------/ + | +C v +[ print(*p) ] +[ return ] +``` + +We will use a notation like `Block/Index` to refer to a specific +statement or terminate in the control-flow graph. `A/0` and `B/4` +refer to `p = &foo` and `goto C`, respectively. + +### What is a lifetime and how does it interact with the borrow checker + +To start with, we will consider lifetimes as a **set of points in the +control-flow graph**; later in the RFC we will extend the domain of +these sets to include "skolemized" lifetimes, which correspond to +named lifetime parameters declared on a function. If a lifetime +contains the point P, that implies that references with that lifetime +are valid on entry to P. Lifetimes appear in various places in the MIR +representation: + +- The types of variables (and temporaries, etc) may contain lifetimes. +- Every borrow expression has a designated lifetime. + +We can extend our example 4 to include explicit lifetime names. There +are three lifetimes that result. We will call them `'p`, `'foo`, and +`'bar`: + +```rust +let mut foo: T = ...; +let mut bar: T = ...; +let p: &'p T; +// -- +p = &'foo foo; +// ---- +if condition { + print(*p); + p = &'bar bar; + // ---- +} +print(*p); +``` + +As you can see, the lifetime `'p` is part of the type of the variable +`p`. It indicates the portions of the control-flow graph where `p` can +safely be dereferenced. The lifetimes `'foo` and `'bar` are different: +they refer to the lifetimes for which `foo` and `bar` are borrowed, +respectively. + +Lifetimes attached to a borrow expression, like `'foo` and `'bar`, are +important to the borrow checker. Those correspond to the portions of +the control-flow graph in which the borrow checker will enforce its +restrictions. In this case, since both borrows are shared borrows +(`&`), the borrow checker will prevent `foo` from being modified +during `'foo` and it will prevent `bar` from being modified during +`'bar`. If these had been mutable borrows (`&mut`), the borrow checker +would have prevented **all** access to `foo` and `bar` during those +lifetimes. + +There are many valid choices one could make for `'foo` and `'bar`. +This RFC however describes an inference algorithm that aims to pick +the **minimal** lifetimes for each borrow which could possibly work. +This corresponds to imposing the fewest restrictions we can. + +In the case of example 4, therefore, we wish our algorithm to compute +that `'foo` is `{A/1, B/0, C/0}`, which notably excludes the points B/1 +through B/4. `'bar` should be inferred to the set `{B/3, B/4, +C/0}`. The lifetime `'p` will be the union of `'foo` and `'bar`, since +it contains all the points where the variable `p` is valid. + +### Lifetime inference constraints + +The inference algorithm works by analyzing the MIR and creating a +series of **constraints**. These constraints obey the following +grammar: + +``` +// A constraint set C: +C = true + | C, (L1: L2) @ P // Lifetime L1 outlives Lifetime L2 at point P + +// A lifetime L: +L = 'a + | {P} +``` + +Here the terminal `P` represents a point in the control-flow graph, +and the notation `'a` refers to some named lifetime inference variable +(e.g., `'p`, `'foo` or `'bar`). + +Once the constraints are created, the **inference algorithm** solves +the constraints. This is done via fixed-point iteration: each +lifetime variable begins as an empty set and we iterate over the +constaints, repeatedly growing the lifetimes until they are big enough +to satisfy all constraints. + +(If you'd like to compare this to the prototype code, the file +[`regionck.rs`] is responsible for creating the constraints, and +[`infer.rs`] is responsible for solving them.) + +[`regionck.rs`]: https://github.com/nikomatsakis/nll/blob/master/nll/src/regionck.rs +[`infer.rs`]: https://github.com/nikomatsakis/nll/blob/master/nll/src/infer.rs + +### Liveness + +One key ingredient to understanding how NLL should work is +understanding **liveness**. The term "liveness" derives from compiler +analysis, but it's fairly intuitive. We say that **a variable is live +if the current value that it holds may be used later**. This is very +important to Example 4: + +```rust +let mut foo: T = ...; +let mut bar: T = ...; +let p: &'p T = &foo; +// `p` is live here: its value may be used on the next line. +if condition { + // `p` is live here: its value will be used on the next line. + print(*p); + // `p` is DEAD here: its value will not be used. + p = &bar; + // `p` is live here: its value will be used later. +} +// `p` is live here: its value may be used on the next line. +print(*p); +// `p` is DEAD here: its value will not be used. +``` + +Here you see a variable `p` that is assigned in the beginning of the +program, and then maybe re-assigned during the `if`. The key point is +that `p` becomes **dead** (not live) in the span before it is +reassigned. This is true even though the variable `p` will be used +again, because the **value** that is in `p` will not be used. + +Traditional compiler compute liveness based on variables, but we wish +to compute liveness for **lifetimes**. We can extend a variable-based +analysis to lifetimes by saying that a lifetime L is live at a point P +if there is some variable `p` which is live at P, and L appears in the +type of `p`. (Later on, when we cover the dropck, we will use a more +selective notion of liveness for lifetimes in which *some* of the +lifetimes in a variable's type may be live while others are not.) So, +in our running example, the lifetime `'p` would be live at precisely +the same points that `p` is live. The lifetimes `'foo` and `'bar` have +no points where they are (directly) live, since they do not appear in +the types of any variables. + + * However, this does not mean these lifetimes are irrelevant; as + shown below, subtyping constraints introduced by subsequent + analyses will eventually require `'foo` and `'bar` to *outlive* + `'p`. + +#### Liveness-based constraints for lifetimes + +The first set of constraints that we generate are derived from +liveness. Specifically, if a lifetime L is live at the point P, +then we will introduce a constraint like: + + (L: {P}) @ P + +(As we'll see later when we cover solving constraints, this constraint +effectively just inserts `P` into the set for `L`. In fact, the +prototype doesn't bother to materialize such constraints, instead just +immediately inserting `P` into `L`.) + +For our running example, this means that we would introduce the following +liveness constraints: + + ('p: {A/1}) @ A/1 + ('p: {B/0}) @ B/0 + ('p: {B/3}) @ B/3 + ('p: {B/4}) @ B/4 + ('p: {C/0}) @ C/0 + +### Subtyping + +Whenever references are copied from one location to another, the Rust +subtyping rules require that the lifetime of the source reference +**outlives** the lifetime of the target location. As discussed +earlier, in this RFC, we extend the notion of subtyping to be +**location-aware**, meaning that we take into account the point where +the value is being copied. + +For example, at the point A/0, our running example contains a borrow +expression `p = &'foo foo`. In this case, the borrow expression will +produce a reference of type `&'foo T`, where `T` is the type of +`foo`. This value is then assigned to `p`, which has the type `&'p T`. +Therefore, we wish to require that `&'foo T` be a subtype of `&'p T`. +Moreover, this relation needs to hold at the point A/1 -- the +**successor** of the point A/0 where the assignment occurs (this is +because the new value of `p` is first visible in A/1). We write that +subtyping constraint as follows: + + (&'foo T <: &'p T) @ A/1 + +The standard Rust subtyping rules (two examples of which are given +below) can then "break down" this subtyping rule into the lifetime +constraints we need for inference: + + (T_a <: T_b) @ P + ('a: 'b) @ P // <-- a constraint for our inference algorithm + ------------------------ + (&'a T_a <: &'b T_b) @ P + + (T_a <: T_b) @ P + (T_b <: T_a) @ P // (&mut T is invariant) + ('a: 'b) @ P // <-- another constraint + ------------------------ + (&'a mut T_a <: &'b mut T_b) @ P + +In the case of our running example, we generate the following subtyping +constraints: + + (&'foo T <: &'p T) @ A/1 + (&'bar T <: &'p T) @ B/3 + +These can be converted into the following lifetime constraints: + + ('foo: 'p) @ A/1 + ('bar: 'p) @ B/3 + +### Reborrow constraints + +There is one final source of constraints. It frequently happens that we +have a borrow expression that "reborrows" the referent of an +existing reference: + + let x: &'x i32 = ...; + let y: &'y i32 = &*x; + +In such cases, there is a connection between the lifetime `'y` of the +borrow and the lifetime `'x` of the original reference. In particular, +`'x` must outlive `'y` (`'x: 'y`). In simple cases like this, the +relationship is the same regardless of whether the original reference +`x` is a shared (`&`) or mutable (`&mut`) reference. However, in more +complex cases that involve multiple dereferences, the treatment is +different. + +**Supporting prefixes.** To define the reborrow constraints, we first +introduce the idea of supporting prefixes -- this definition will be +useful in a few places. The *supporting prefixes* for an lvalue are +formed by stripping away fields and derefs, except that we stop when +we reach the deref of a shared reference. Inituitively, shared +references are different because they are `Copy` -- and hence one +could always copy the shared reference into a temporary and get an +equivalent path. Here are some examples of supporting prefixes: + +``` +let r: (&(i32, i64), (f32, f64)); + +// The path (*r.0).1 has type `i64` and supporting prefixes: +// - (*r.0).1 +// - *r.0 + +// The path r.1.0 has type `f32` and supporting prefixes: +// - r.1.0 +// - r.1 +// - r + +let m: (&mut (i32, i64), (f32, f64)); + +// The path (*m.0).1 has type `i64` and supporting prefixes: +// - (*m.0).1 +// - *m.0 +// - m.0 +// - m +``` + +**Reborrow constraints.** Consider the case where we have a borrow +(shared or mutable) of some lvalue `lv_b` for the lifetime `'b`: + + lv_l = &'b lv_b // or: + lv_l = &'b mut lv_b + +In that case, we compute the supporting prefixes of `lv_b`, and find +every deref lvalue `*lv` in the set where `lv` is a reference with +lifetime `'a`. We then add a constraint `('a: 'b) @ P`, where `P` is +the point following the borrow (that's the point where the borrow +takes effect). + +Let's look at some examples. In each case, we will link to the +corresponding test from the prototype implementation. + +[**Example 1.**][bck-rvwbi] To see why this rule is needed, let's +first consider a simple example involving a single reference: + +[bck-rvwbi]: https://github.com/nikomatsakis/nll/blob/master/test/borrowck-read-variable-while-borrowed-indirect.nll + +```rust +let mut foo: i32 = 22; +let r_a: &'a mut i32 = &'a mut foo; +let r_b: &'b mut i32 = &'b mut *r_a; +... +use(r_b); +``` + +In this case, the supporting prefixes of `*r_a` are `*r_a` and `r_a` +(because `r_a` is a mutable reference, we recurse). Only one of those, +`*r_a`, is a deref lvalue, and the reference `r_a` being dereferenced +has the lifetime `'a`. We would add the constraint that `'a: 'b`, +thus ensuring that `foo` is considered borrowed so long as `r_b` is in +use. Without this constraint, the lifetime `'a` would end after the +second borrow, and hence `foo` would be considered unborrowed, even +though `*r_b` could still be used to access `foo`. + +[**Example 2.**][bck-wvare] Consider now a case with a double indirection: + +[bck-wvare]: https://github.com/nikomatsakis/nll/blob/master/test/borrowck-write-variable-after-ref-extracted.nll + +```rust +let mut foo: i32 = 22; +let mut r_a: &'a i32 = &'a foo; +let r_b: &'b &'a i32 = &'b r_a; +let r_c: &'c i32 = &'c **r_b; +// What is considered borrowed here? +use(r_c); +``` + +Just as before, it is important that, so long as `r_c` is in use, +`foo` is considered borrowed. However, what about the variable `r_a`: +should *it* considered borrowed? The answer is no: once `r_c` is +initialized, the value of `r_a` is no longer important, and it would +be fine to (for example) overwrite `r_a` with a new value, even as +`foo` is still considered borrowed. This result falls out from our +reborrowing rules: the supporting paths of `**r_b` is just `**r_b`. +We do not add any more paths because this path is already a +dereference of `*r_b`, and `*r_b` has (shared reference) type `&'a +i32`. Therefore, we would add one reborrow constraint: that `'a: 'c`. +This constraint ensures that as long as `r_c` is in use, the borrow of +`foo` remains in force, but the borrow of `r_a` (which has the +lifetime `'b`) can expire. + +[**Example 3.**][bck-rrwrmb] The previous example showed how a borrow +of a shared reference can expire once it has been dereferenced. With +mutable references, however, this is not safe. Consider the following example: + +[bck-rrwrmb]: https://github.com/nikomatsakis/nll/blob/master/test/borrowck-read-ref-while-referent-mutably-borrowed.nll + +```rust +let foo = Foo { ... }; +let p: &'p mut Foo = &mut foo; +let q: &'q mut &'p mut Foo = &mut p; +let r: &'r mut Foo = &mut **q; +use(*p); // <-- This line should result in an ERROR +use(r); +``` + +The key point here is that we create a reference `r` by reborrowing +`**q`; `r` is then later used in the final line of the program. This +use of `r` must extend the lifetime of the borrows used to create +*both* `p` *and* `q`. Otherwise, one could access (and mutate) the +same memory through both `*r` and `*p`. (In fact, the real rustc did +in its early days have a soundness bug much like this one.) + +Because dereferencing a mutable reference does not stop the supporting +prefixes from being enumerated, the supporting prefixes of `**q` are +`**q`, `*q`, and `q`. Therefore, we add two reborrow constraints: `'q: +'r` and `'p: 'r`, and hence both borrows are indeed considered in +scope at the line in question. + +As an alternate way of looking at the previous example, consider it +like this. To create the mutable reference `p`, we get a "lock" on +`foo` (that lasts so long as `p` is in use). We then take a lock on +the mutable reference `p` to create `q`; this lock must last for as +long as `q` is in use. When we create `r` by borrowing `**q`, that is +the last direct use of `q` -- so you might think we can release the +lock on `p`, since `q` is no longer in (direct) use. However, that +would be unsound, since then `r` and `*p` could both be used to access +the same memory. The key is to recognize that `r` represents an +indirect use of `q` (and `q` in turn is an indirect use of `p`), and +hence so long as `r` is in use, `p` and `q` must also be considered "in +use" (and hence their "locks" still enforced). + +### Solving constraints + +Once the constraints are created, the **inference algorithm** solves +the constraints. This is done via fixed-point iteration: each +lifetime variable begins as an empty set and we iterate over the +constaints, repeatedly growing the lifetimes until they are big enough +to satisfy all constraints. + +The meaning of a constraint like `('a: 'b) @ P` is that, starting from +the point P, the lifetime `'a` must include all points in `'b` that +are reachable from the point P. The implementation +[does a depth-first search starting from P][dfs]; the search stops if +we exit the lifetime `'b`. Otherwise, for each point we find, we add +it to `'a`. + +In our example, the full set of constraints is: + + ('foo: 'p) @ A/1 + ('bar: 'p) @ B/3 + ('p: {A/1}) @ A/1 + ('p: {B/0}) @ B/0 + ('p: {B/3}) @ B/3 + ('p: {B/4}) @ B/4 + ('p: {C/0}) @ C/0 + +Solving these constraints results in the following lifetimes, +which are precisely the answers we expected: + + 'p = {A/1, B/0, B/3, B/4, C/0} + 'foo = {A/1, B/0, C/0} + 'bar = {B/3, B/4, C/0} + +[dfs]: https://github.com/nikomatsakis/nll/blob/1cff361c9aeb6f553b528078866f5717f1872dad/nll/src/infer.rs#L71-L113 + +### Intuition for why this algorithm is correct + +For the algorithm to be correct, there is a critical invariant that we +must maintain. Consider some path H that is borrowed with lifetime L +at a point P to create a reference R; this reference R (or some +copy/move of it) is then later dereferenced at some point Q. + +We must ensure that the reference has not been invalidated: this means +that the memory which was borrowed must not have been freed by the +time we reach Q. If the reference R is a shared reference (`&T`), then +the memory must also not have been written (modulo `UnsafeCell`). If +the reference R is a mutable reference (`&mut T`), then the memory +must not have been accessed at all, except through the reference R. +**To guarantee these properties, we must prevent actions that might +affect the borrowed memory for all of the points between P (the +borrow) and Q (the use).** + +This means that L must at least include all the points between P and +Q. There are two cases to consider. First, the case where the access +at point Q occurs through the same reference R that was created by +the borrow: + + R = &H; // point P + ... + use(R); // point Q + +In this case, the variable R will be **live** on all the points +between P and Q. The liveness-based rules suffice for this case: +specifically, because the type of R includes the lifetime L, we know +that L must include all the points between P and Q, since R is live +there. + +The second case is when the memory referenced by R is accessed, but +through an alias (or move): + + R = &H; // point P + R2 = R; // last use of R, point A + ... + use(R2); // point Q + +In this case, the liveness rules alone do not suffice. The problem is +that the `R2 = R` assignment may well be the last use of R, and so the +**variable** R is dead at this point. However, the *value* in R will +still be dereferenced later (through R2), and hence we want the +lifetime L to include those points. This is where the **subtyping +constraints** come into play: the type of R2 includes a lifetime L2, +and the assignment `R2 = R` will establish an outlives constraint `(L: +L2) @ A` between L and L2. Moreover, this new variable R2 must be +live between the assignment and the ultimate use (that is, along the +path A...Q). Putting these two facts together, we see that L will +ultimately include the points from P to A (because of the liveness of +R) and the points from A to Q (because the subtyping requirement +propagates the liveness of R2). + +Note that it is possible for these lifetimes to have gaps. This can occur +when the same variable is used and overwritten multiple times: + + let R: &L i32; + let R2: &L2 i32; + + R = &H1; // point P1 + R2 = R; // point A1 + use(R2); // point Q1 + ... + R2 = &H2; // point P2 + use(R2); // point Q2 + +In this example, the liveness constraints on R2 will ensure that L2 +(the lifetime in its type) includes Q1 and Q2 (because R2 is live at +those two points), but not the "..." nor the points P1 or P2. Note +that the subtyping relationship (`(L: L2) @ A1)`) at A1 here ensures +that L also includes Q1, but doesn't require that L includes Q2 (even +though L2 has point Q2). This is because the value in R2 at Q2 cannot +have come from the assignment at A1; if it could have done, then +either R2 would have to be live between A1 and Q2 or else there would +be a subtyping constraint. + +### Other examples + +Let us work through some more examples. We begin with problem cases #1 +and #2 (problem case #3 will be covered after we cover named lifetimes +in a later section). + +#### Problem case #1. + +Translated into MIR, the example will look roughly as follows: + +```rust +let mut data: Vec; +let slice: &'slice mut i32; +START { + data = ...; + slice = &'borrow mut data; + capitalize(slice); + data.push('d'); + data.push('e'); + data.push('f'); +} +``` + +The constraints generated will be as follows: + + ('slice: {START/2}) @ START/2 + ('borrow: 'slice) @ START/2 + +Both `'slice` and `'borrow` will therefore be inferred to START/2, and +hence the accesses to `data` in START/3 and the following statements +are permitted. + +#### Problem case #2. + +Translated into MIR, the example will look roughly as follows (some +irrelevant details are elided). Note that the `match` statement is +translated into a SWITCH, which tests the variant, and a "downcast", +which lets us extract the contents out from the `Some` variant (this +operation is specific to MIR and has no Rust equivalent, other than as +part of a match). + +``` +let map: HashMap; +let key: K; +let tmp0: &'tmp0 mut HashMap; +let tmp1: &K; +let tmp2: Option<&'tmp2 mut V>; +let value: &'value mut V; + +START { +/*0*/ map = ...; +/*1*/ key = ...; +/*2*/ tmp0 = &'map mut map; +/*3*/ tmp1 = &key; +/*4*/ tmp2 = HashMap::get_mut(tmp0, tmp1); +/*5*/ SWITCH tmp2 { None => NONE, Some => SOME } +} + +NONE { +/*0*/ ... +/*1*/ goto EXIT; +} + +SOME { +/*0*/ value = tmp2.downcast.0; +/*1*/ process(value); +/*2*/ goto EXIT; +} + +EXIT { +} +``` + +The following liveness constraints are generated: + + ('tmp0: {START/3}) @ START/3 + ('tmp0: {START/4}) @ START/4 + ('tmp2: {SOME/0}) @ SOME/0 + ('value: {SOME/1}) @ SOME/1 + +The following subtyping-based constraints are generated: + + ('map: 'tmp0) @ START/3 + ('tmp0: 'tmp2) @ START/5 + ('tmp2: 'value) @ SOME/1 + +Ultimately, the lifetime we are most interested in is `'map`, +which indicates the duration for which `map` is borrowed. If we solve +the constraints above, we will get: + + 'map == {START/3, START/4, SOME/0, SOME/1} + 'tmp0 == {START/3, START/4, SOME/0, SOME/1} + 'tmp2 == {SOME/0, SOME/1} + 'value == {SOME/1} + +These results indicate that `map` **can** be mutated in the `None` +arm; `map` could also be mutated in the `Some` arm, but only after +`process()` is called (i.e., starting at SOME/2). This is the desired +result. + +#### Example 4, invariant + +It's worth looking at a variant of our running example ("Example 4"). +This is the same pattern as before, but instead of using `&'a T` +references, we use `Foo<'a>` references, which are **invariant** with +respect to `'a`. This means that the `'a` lifetime in a `Foo<'a>` +value cannot be approximated (i.e., you can't make it shorter, as you +can with a normal reference). Usually invariance arises because of +mutability (e.g., `Foo<'a>` might have a field of type `Cell<&'a +()>`). The key point here is that invariance actually makes **no +difference at all** the outcome. This is true because of +location-based subtyping. + +```rust +let mut foo: T = ...; +let mut bar: T = ...; +let p: Foo<'a>; + +p = Foo::new(&foo); +if condition { + print(*p); + p = Foo::new(&bar); +} +print(*p); +``` + +Effectively, we wind up with the same constraints as before, but where +we only had `'foo: 'p`/`'bar: 'p` constraints before (due to subtyping), we now +also have `'p: 'foo` and `'p: 'bar` constraints: + + ('foo: 'p) @ A/1 + ('p: 'foo) @ A/1 + ('bar: 'p) @ B/3 + ('p: 'bar) @ B/3 + ('p: {A/1}) @ A/1 + ('p: {B/0}) @ B/0 + ('p: {B/3}) @ B/3 + ('p: {B/4}) @ B/4 + ('p: {C/0}) @ C/0 + +The key point is that the new constraints don't affect the final answer: +the new constraints were already satisfied with the older answer. + +#### vec-push-ref + +In previous iterations of this proposal, the location-aware subtyping +rules were replaced with transformations such as SSA form. The +vec-push-ref example demonstrates the value of location-aware +subtyping in contrast to these approaches. + +```rust +let foo: i32; +let vec: Vec<&'vec i32>; +let p: &'p i32; + +foo = ...; +vec = Vec::new(); +p = &'foo foo; +if true { + vec.push(p); +} else { + // Key point: `foo` not borrowed here. + use(vec); +} +``` + +This can be converted to control-flow graph form: + +``` +block START { + v = Vec::new(); + p = &'foo foo; + goto B C; +} + +block B { + vec.push(p); + goto EXIT; +} + +block C { + // Key point: `foo` not borrowed here + use(vec); + goto EXIT; +} + +block EXIT { +} +``` + +Here the relations from liveness are: + + ('vec: {START/1}) @ START/1 + ('vec: {START/2}) @ START/2 + ('vec: {B/0}) @ B/0 + ('vec: {C/0}) @ C/0 + ('p: {START/2}) @ START/2 + ('p: {B/0}) @ B/0 + +Meanwhile, the call to `vec.push(p)` establishes this subtyping +relation: + + ('p: 'vec) @ B/1 + ('foo: 'p) @ START/2 + +The solution is: + + 'vec = {START/1, START/2, B/0, C/0} + 'p = {START/2, B/0} + 'foo = {START/2, B/0} + +What makes this example interesting is that **the lifetime `'vec` must +include both halves of the `if`** -- because it is used in both branches +-- but `'vec` only becomes "entangled" with the lifetime `'p` on one +path. Thus even though `'vec` has to outlive `'p`, `'p` never winds up +including the "else" branch thanks to location-aware subtyping. + +## Layer 2: Avoiding infinite loops + +The previous design was described in terms of the "pure" MIR +control-flow graph. However, using the raw graph has some undesirable +properties around infinite loops. In such cases, the graph has no +exit, which undermines the traditional definition of reverse analyses +like liveness. To address this, when we build the control-flow graph +for our functions, we will augment it with additional edges -- in +particular, for every infinite loop (`loop { }`), we will add false +"unwind" edges. This ensures that the control-flow graph has a final +exit node (the success of the RETURN and RESUME nodes) that +postdominates all other nodes in the graph. + +If we did not add such edges, the result would also allow a number of surprising +programs to type-check. For example, it would be possible to borrow local variables +with `'static` lifetime, so long as the function never returned: + +```rust +fn main() { + let x: usize; + let y: &'static x = &x; + loop { } +} +``` + +This would work because (as covered in detail under the borrow check +section) the `StorageDead(x)` instruction would never be reachable, +and hence any lifetime of borrow would be acceptable. This further leads to +other surprising programs that still type-check, such as this example which +uses an (incorrect, but declared as unsafe) API for spawning threads: + +```rust +let scope = Scope::new(); +let mut foo = 22; + +unsafe { + // dtor joins the thread + let _guard = scope.spawn(&mut foo); + loop { + foo += 1; + } + // drop of `_guard` joins the thread +} +``` + +Without the unwind edges, this code would pass the borrowck, since the +drop of `_guard` (and `StorageDead` instruction) is not reachable, and +hence `_guard` is not considered live (after all, its destructor will +indeed never run). However, this would permit the `foo` variable to be +modified both during the infinite loop and by the thread launched by +`scope.spawn()`, which was given access to an `&mut foo` reference +(albeit one with a theoretically short lifetime). + +With the false unwind edge, the compiler essentially always assumes +that a destructor *may* run, since every scope may theoretically +execute. This extends the `&mut foo` borrow given to `scope.spawn()` +to cover the body of the loop, resulting in a borrowck error. + +## Layer 3: Accommodating dropck + +MIR includes an action that corresponds to "dropping" a variable: + + DROP(variable) + +Note that while MIR supports general drops of any lvalue, at the point +where this analysis is running, we are always dropping entire +variables at a time. This operation executes the destructor for +`variable`, effectively "de-initializing" the memory in which the +value resides (if the variable -- or parts of the variable -- have +already been dropped, then drop has no effect; this is not relevant to +the current analysis). + +Interestingly, in many cases dropping a value does not require that the +lifetimes in the dropped value be valid. After all, dropping a +reference of type `&'a T` or `&'a mut T` is defined as a no-op, so it +does not matter if the reference points at valid memory. In cases like +this, we say that the lifetime `'a` **may dangle**. This is inspired by the C +term "dangling pointer" which means a pointer to freed or invalid +memory. + +However, if that same reference is stored in the field of a struct +which implements the `Drop` trait, when the struct may, during its +destructor, access the referenced value, so it's very important that +the reference be valid in that case. Put another way, if you have a +value `v` of type `Foo<'a>` that implements `Drop`, then `'a` +typically **cannot dangle** when `v` is dropped (just as `'a` would +not be allowed to dangle for any other operation). + +More generally, RFC 1327 defined specific rules for which lifetimes in +a type may dangle during drop and which may not. We integrate those +rules into our liveness analysis as follows: the MIR instruction +`DROP(variable)` is not treated like other MIR instructions when it +comes to liveness. In a sense, conceptually we run two distinct liveness analyses (in practice, the prototype +uses two bits per variable): + +1. The first, which we've already seen, indicates when a variable's + current value may be **used** in the future. This corresponds to + "non-drop" uses of the variable in the MIR. Whenever a variable is live by this definition, + all of the lifetimes in its type are live. +2. The second, which we are adding now, indicates when a variable's + current value may be **dropped** in the future. This corresponds to + "drop" uses of the variable in the MIR. Whenever a variable is live + in *this* sense, all of the lifetimes in its type **except those + marked as may-dangle** are live. + +Permitting lifetimes to dangle during drop is very important! In fact, +it is essential to even the most basic non-lexical lifetime examples, +such as Problem Case #1. After all, if we translate Problem Case #1 +into MIR, we see that the reference `slice` will wind up being dropped +at the end of the block: + +```rust +let mut data: Vec; +let slice: &'slice mut i32; +START { + ... + slice = &'borrow mut data; + capitalize(slice); + data.push('d'); + data.push('e'); + data.push('f'); + DROP(slice); + DROP(data); +} +``` + +This poses no problem for our analysis, however, because `'slice` "may +dangle" during the drop, and hence is not considered live. + +## Layer 4: Named lifetimes + +Until now, we've only considered lifetimes that are confined to the +extent of a function. Often, we want to reason about +lifetimes that begin or end after the current function has ended. More +subtly, we sometimes want to have lifetimes that sometimes begin and +end in the current function, but which may (along some paths) extend +into the caller. Consider Problem Case #3 (the corresponding test case +in the prototype is the [get-default] test): + +[get-default]: https://github.com/nikomatsakis/nll/blob/master/test/get-default.nll + +```rust +fn get_default<'r,K,V:Default>(map: &'r mut HashMap, + key: K) + -> &'r mut V { + match map.get_mut(&key) { // -------------+ 'r + Some(value) => value, // | + None => { // | + map.insert(key, V::default()); // | + // ^~~~~~ ERROR // | + map.get_mut(&key).unwrap() // | + } // | + } // | +} // v +``` + +When we translate this into MIR, we get something like the following +(this is "pseudo-MIR"): + +``` +block START { + m1 = &'m1 mut *map; // temporary created for `map.get_mut()` call + v = Map::get_mut(m1, &key); + switch v { SOME NONE }; +} + +block SOME { + return = v.as.0; // assign to return value slot + goto END; +} + +block NONE { + Map::insert(&*map, key, ...); + m2 = &'m2 mut *map; // temporary created for `map.get_mut()` call + v = Map::get_mut(m2, &key); + return = ... // "unwrap" of `v` + goto END; +} + +block END { + return; +} +``` + +The key to this example is that the first borrow of `map`, with the +lifetime `'m1`, must extend to the end of the `'r`, but only if we +branch to SOME. Otherwise, it should end once we enter the NONE block. + +To accommodate cases like this, we will extend the notion of a region +so that it includes not only points in the control-flow graph, but +also includes a (possibly empty) set of "end regions" for various +named lifetimes. We denote these as `end('r)` for some named region +`'r`. The region `end('r)` can be understood semantically as referring +to some portion of the caller's control-flow graph (actually, they +could extend beyond the end of the caller, into the caller's caller, +and so forth, but that doesn't concern us). This new region might then +be denoted as the following (in pseudocode form): + +```rust +struct Region { + points: Set, + end_regions: Set, +} +``` + +In this case, when a type mentions a named lifetime, such as `'r`, that +can be represented by a region that includes: + +- the entire CFG, +- and, the end region for that named lifetime (`end('r)`). + +Furthermore, we can **elaborate** the set to include `end('x)` for +every named lifetime `'x` such that `'r: 'x`. This is because, if `'r: +'x`, then we know that `'r` doesn't end up until `'x` has already +ended. + +Finally, we must adjust our definition of subtyping to accommodate +this amended definition of a region, which we do as follows. When we have +an outlives relation + + 'b: 'a @ P + +where the end point of the CFG is reachable from P without leaving +`'a`, the existing inference algorithm would simply add the end-point +to `'b` and stop. The new algorithm would also add any end regions +that are included in `'a` to `'b` at that time. (Expressed less +operationally, `'b` only outlives `'a` if it also includes the +end-regions that `'a` includes, presuming that the end point of the +CFG is reachable from P). The reason that we require the end point of +the CFG to be reachable is because otherwise the data never escapes +the current function, and hence `end('r)` is not reachable (since +`end('r)` only covers the code in callers that executes *after* the +return). + +NB: This part of the prototype is partially +implemented. [Issue #12](https://github.com/nikomatsakis/nll/issues/12) +describes the current status and links to the in-progress PRs. + +## Layer 5: How the borrow check works + +For the most part, the focus of this RFC is on the structure of +lifetimes, but it's worth talking a bit about how to integrate +these non-lexical lifetimes into the borrow checker. In particular, +along the way, we'd like to fix two shortcomings of the borrow checker: + +**First, support nested method calls like `vec.push(vec.len())`.** +Here, the plan is to continue with the `mut2` borrow solution proposed +in [RFC 2025]. This RFC does not (yet) propose one of the type-based +solutions described in RFC 2025, such as "borrowing for the future" or +`Ref2`. The reasons why are discussed in the Alternatives section. For +simplicity, this description of the borrow checker ignores +[RFC 2025]. The extensions described here are fairly orthogonal to the +changes proposed in [RFC 2025], which in effect cause the start of a +borrow to be delayed. + +**Second, permit variables containing mutable references to be +modified, even if their referent is borrowed.** This refers to the +"Problem Case #4" described in the introduction; we wish to accept the +original program. + +### Borrow checker phase 1: computing loans in scope + +The first phase of the borrow checker computes, at each point in +the CFG, the set of in-scope **loans**. A "loan" is represented as a tuple +`('a, shared|uniq|mut, lvalue)` indicating: + +1. the lifetime `'a` for which the value was borrowed; +2. whether this was a shared, unique, or mutable loan; + - "unique" loans are exactly like mutable loans, but they do not permit + mutation of their referents. They are used only in closure desugarings + and are not part of Rust's surface syntax. +3. the lvalue that was borrowed (e.g., `x` or `(*x).foo`). + +The set of in-scope loans at each point is found via a fixed-point +dataflow computation. We create a loan tuple from each borrow rvalue +in the MIR (that is, every assignment statement like `tmp = &'a +b.c.d`), giving each tuple a unique index `i`. We can then represent +the set of loans that are in scope at a particular point using a +bit-set and do a standard forward data-flow propagation. + +For a statement at point P in the graph, we define the "transfer +function" -- that is, which loans it brings into or out of scope -- as +follows: + +- any loans whose region does not include P are killed; +- if this is a borrow statement, the corresponding loan is generated; +- if this is an assignment `lv = `, then any loan for some path P + of which `lv` is a prefix is killed. + +The last point bears some elaboration. This rule is what allows us to +support cases like the one in Problem Case #4: + +```rust +let list: &mut List = ...; +let v = &mut (*list).value; +list = ...; // <-- assignment +``` + +At the point of the marked assignment, the loan of `(*list).value` is +in-scope, but it does not have to be considered in-scope +afterwards. This is because the variable `list` now holds a fresh +value, and that new value has not yet been borrowed (or else we could +not have produced it). Specifically, whenever we see an assignment `lv += ` in MIR, we can clear all loans where the borrowed path +`lv_loan` has `lv` as a prefix. (In our example, the assignment is to +`list`, and the loan path `(*list).value` has `list` as a prefix.) + +**NB.** In this phase, when there is an assignment, we always clear +all loans that applied to the overwritten path; however, in some cases +the **assignment itself** may be illegal due to those very loans. In +our example, this would be the case if the type of `list` had been +`List` and not `&mut List`. In such cases, errors will be +reported by the next portion of the borrowck, described in the next +section. + +### Borrow checker phase 2: reporting errors + +At this point, we have computed which loans are in scope at each +point. Next, we traverse the MIR and identify actions that are illegal +given the loans in scope. Rather than go through every kind of MIR statement, +we can break things down into two kinds of actions that can be performed: + +- Accessing an lvalue, which we categorize along two axes (shallow vs deep, read vs write) +- Dropping an lvalue + +For each of these kinds of actions, we will specify below the rules +that determine when they are legal, given the set of loans L in scope +at the start of the action. The second phase of the borrow check +therefore consists of iterating over each statement in the MIR and +checking, given the in-scope loans, whether the actions it performs +are legal. Translating MIR statements into actions is mostly +straightforward: + +- A `StorageDead` statement counts as a **shallow write**. +- An assignment statement `LV = RV` is a **shallow write** to `LV`; +- and, within the rvalue `RV`: + - Each lvalue operand is either a **deep read** or a **deep write** action, depending + on whether or not the type of the lvalue implements `Copy`. + - Note that moves count as "deep writes". + - A shared borrow `&LV` counts as a **deep read**. + - A mutable borrow `&mut LV` counts as **deep write**. + +There are a few interesting cases to keep in mind: + +- MIR models discriminants more precisely. They should be + thought of as a distinct *field* when it comes to borrows. +- In the compiler today, `Box` is still "built-in" to MIR. This RFC + ignores that possibility and instead acts as though borrowed + references (`&` and `&mut`) and raw pointers (`*const` and `*mut`) + were the only sorts of pointers. It should be straight-forward to + extend the text here to cover `Box`, though some questions arise + around the handling of drop (see the section on drops for details). + +**Accessing an lvalue LV.** When accessing an lvalue LV, there are two +axes to consider: + +- The access can be SHALLOW or DEEP: + - A *shallow* access means that the immediate fields reached at LV + are accessed, but references or pointers found within are not + dereferenced. Right now, the only access that is shallow is an + assignment like `x = ...`, which would be a **shallow write** of + `x`. + - A *deep* access means that all data reachable through a given lvalue + may be invalidated or accessed by this action. +- The access can be a READ or WRITE: + - A *read* means that the existing data may be read, but will not be changed. + - A *write* means that the data may be mutated to new values or + otherwise invalidated (for example, it could be de-initialized, as + in a move operation). + +"Deep" accesses are often deep because they create and release an +alias, in which case the "deep" qualifier reflects what might happen +through that alias. For example, if you have `let x = &mut y`, that is +considered a **deep write** of `y`, even though the **actual borrow** +doesn't do anything at all, we create a mutable alias `x` that can be +used to mutate anything reachable from `y`. A move `let x = y` is +similar: it writes to the shallow content of `y`, but then -- via the +new name `x` -- we can access all other content accessible through +`y`. + +The pseudocode for deciding when an access is legal looks like this: + +``` +fn access_legal(lvalue, is_shallow, is_read) { + let relevant_borrows = select_relevant_borrows(lvalue, is_shallow); + + for borrow in relevant_borrows { + // shared borrows like `&x` still permit reads from `x` (but not writes) + if is_read && borrow.is_read { continue; } + + // otherwise, report an error, because we have an access + // that conflicts with an in-scope borrow + report_error(); + } +} +``` + +As you can see, it works in two steps. First, we enumerate a set of +in-scope borrows that are relevant to `lvalue` -- this set is affected +by whether this is a "shallow" or "deep" action, as will be described +shortly. Then, for each such borrow, we check if it conflicts with the +action (i.e.,, if at least one of them is potentially writing), and, +if so, we report an error. + +For **shallow** accesses to the path `lvalue`, we consider borrows relevant +if they meet one of the following criteria: + +- there is a loan for the path `lvalue`; + - so: writing a path like `a.b.c` is illegal if `a.b.c` is borrowed +- there is a loan for some prefix of the path `lvalue`; + - so: writing a path like `a.b.c` is illegal if `a` or `a.b` is borrowed +- `lvalue` is a **shallow prefix** of the loan path + - shallow prefixes are found by stripping away fields, but stop at + any dereference + - so: writing a path like `a` is illegal if `a.b` is borrowed + - but: writing `a` is legal if `*a` is borrowed, whether or not `a` + is a shared or mutable reference + +For **deep** accesses to the path `lvalue`, we consider borrows relevant +if they meet one of the following criteria: + +- there is a loan for the path `lvalue`; + - so: reading a path like `a.b.c` is illegal if `a.b.c` is mutably borrowed +- there is a loan for some prefix of the path `lvalue`; + - so: reading a path like `a.b.c` is illegal if `a` or `a.b` is mutably borrowed +- `lvalue` is a **supporting prefix** of the loan path + - supporting prefixes were defined earlier + - so: reading a path like `a` is illegal if `a.b` is mutably + borrowed, but -- in contrast with shallow accesses -- reading `a` is also + illegal if `*a` is mutably borrowed + +**Dropping an lvalue LV.** Dropping an lvalue can be treated as a DEEP +WRITE, like a move, but this is overly conservative. The rules here +are under active development, see +[#40](https://github.com/nikomatsakis/nll-rfc/issues/40). + +# How We Teach This +[how-we-teach-this]: #how-we-teach-this + +## Terminology + +In this RFC, I've opted to continue using the term "lifetime" to refer +to the portion of the program in which a reference is in active use +(or, alternatively, to the "duration of a borrow"). As the intro to +the RFC makes clear, this terminology somewhat conflicts with an +alternative usage, in which lifetime refers to the dynamic extent of a +value (what we call the "scope"). I think that -- if we were starting +over -- it might have been preferable to find an alternative term that +is more specific. However, it would be rather difficult to try and +change the term "lifetime" at this point, and hence this RFC does not +attempt do so. To avoid confusion, however, it seems best if the error +messages result from the region and borrow check avoid the term +lifetime where possible, or use qualification to make the meaning more +clear. + +## Leveraging intuition: framing errors in terms of points + +Part of the reason that Rust currently uses lexical scopes to +determine lifetimes is that it was thought that they would be simpler +for users to reason about. Time and experience have not borne this +hypothesis out: for many users, the fact that borrows are +"artificially" extended to the end of the block is more surprising +than not. Furthermore, most users have a pretty intuitive +understanding of control flow (which makes sense: you have to, in +order to understand what your program will do). + +We therefore propose to leverage this intution when explaining borrow +and lifetime errors. To the extent possible, we will try to explain +all errors in terms of three points: + +- The point where the borrow occurred (B). +- The point where the resulting reference is used (U). +- An intervening point that might have invalidated the reference (A). + +We should select three points such that B can reach A and A can reach +U. In general, the approach is to describe the errors in "narrative" form: + +- First, value is borrowed occurs. +- Next, the action occurs, invalidating the reference. +- Finally, the next use occcurs, after the reference has been invalidated. + +This approach is similar to what we do today, but we often neglect to +mention this third point, where the next use occurs. Note that the +"point of error" remains the *second* action -- that is, the error, +conceptually, is to perform an invalidating action in between two uses +of the reference (rather than, say, to use the reference after an +invalidating action). This actually reflects the definition of +undefined behavior more accurately (that is, performing an illegal +write is what causes undefined behavior, but the write is illegal +because of the latter use). + +To see the difference, consider this erroneous program: + +```rust +fn main() { + let mut i = 3; + let x = &i; + i += 1; + println!("{}", x); +} +``` + +Currently, we emit the following error: + +``` +error[E0506]: cannot assign to `i` because it is borrowed + --> :4:5 + | + 3 | let x = &i; + | - borrow of `i` occurs here + 4 | i += 1; + | ^^^^^^ assignment to borrowed `i` occurs here +``` + +Here, the points B and A are highlighted, but not the point of use +U. Moreover, the "blame" is placed on the assignment. Under this RFC, +we would display the error as follows: + +``` +error[E0506]: cannot write to `i` while borrowed + --> :4:5 + | + 3 | let x = &i; + | - (shared) borrow of `i` occurs here + 4 | i += 1; + | ^^^^^^ write to `i` occurs here, while borrow is still active + 5 | println!("{}", x); + | - borrow is later used here +``` + +Another example, this time using a `match`: + +```rust +fn main() { + let mut x = Some(3); + match &mut x { + Some(i) => { + x = None; + *i += 1; + } + None => { + x = Some(0); // OK + } + } +} +``` + +The error might be: + +``` +error[E0506]: cannot write to `x` while borrowed + --> :4:5 + | + 3 | match &mut x { + | ------ (mutable) borrow of `x` occurs here + 4 | Some(i) => { + 5 | x = None; + | ^^^^^^^^ write to `x` occurs here, while borrow is still active + 6 | *i += 1; + | -- borrow is later used here + | +``` + +(Note that the assignment in the `None` arm is not an error, since the +borrow is never used again.) + +## Some special cases + +There are some cases where the three points are not all visible +in the user syntax where we may need some careful treatment. + +### Drop as last use + +There are times when the last use of a variable will in fact be its +destructor. Consider an example like this: + +```rust +struct Foo<'a> { field: &'a u32 } +impl<'a> Drop for Foo<'a> { .. } + +fn main() { + let mut x = 22; + let y = Foo { field: &x }; + x += 1; +} +``` + +This code would be legal, but for the destructor on `y`, which will +implicitly execute at the end of the enclosing scope. The error +message might be shown as follows: + +``` +error[E0506]: cannot write to `x` while borrowed + --> :4:5 + | + 6 | let y = Foo { field: &x }; + | -- borrow of `x` occurs here + 7 | x += 1; + | ^ write to `x` occurs here, while borrow is still active + 8 | } + | - borrow is later used here, when `y` is dropped +``` + +### Method calls + +One example would be method calls: + +```rust +fn main() { + let mut x = vec![1]; + x.push(x.pop().unwrap()); +} +``` + +We propose the following error for this sort of scenario: + +``` +error[E0506]: cannot write to `x` while borrowed + --> :4:5 + | + 3 | x.push(x.pop().unwrap()); + | - ---- ^^^^^^^^^^^^^^^^ + | | | write to `x` occurs here, while borrow is still in active use + | | borrow is later used here, during the call + | `x` borrowed here +``` + +If you are not using a method, the error would look slightly different, +but be similar in concept: + +``` +error[E0506]: cannot assign to `x` because it is borrowed + --> :4:5 + | + 3 | Vec::push(&mut x, x.pop().unwrap()); + | --------- ------ ^^^^^^^^^^^^^^^^ + | | | write to `x` occurs here, while borrow is still in active use + | | `x` borrowed here + | borrow is later used here, during the call +``` + +We can detect this scenario in MIR readily enough by checking when the +point of use turns out to be a "call" terminator. We'll have to tweak +the spans to get everything to look correct, but that is easy enough. + +### Closures + +As today, when the initial borrow is part of constructing a closure, +we wish to highlight not only the point where the closure is +constructed, but the point *within* the closure where the variable in +question is used. + +## Borrowing a variable for longer than its scope + +Consider this example: + +```rust +let p; +{ + let x = 3; + p = &x; +} +println!("{}", p); +``` + +In this example, the reference `p` refers to `x` with a lifetime that +exceeds the scope of `x`. In short, that portion of the stack will be +popped with `p` still in active use. In today's compiler, this is +detected during the borrow checker by a special check that computes +the "maximal scope" of the path being borrowed (`x`, here). This makes +sense in the existing system since lifetimes and scopes are expressed +in the same units (portions of the AST). In the newer, non-lexical +formulation, this error would be detected somewhat differently. As +described earlier, we would see that a `StorageDead` instruction frees +the slot for `x` while `p` is still in use. We can thus present the +error in the same "three-point style": + +``` +error[E0506]: variable goes out of scope while still borrowed + --> :4:5 + | + 3 | p = &x; + | - `x` borrowed here + 4 | } + | ^ `x` goes out of scope here, while borrow is still in active use + 5 | println!("{}", p); + | - borrow used here, after invalidation +``` + +## Errors during inference + +The remaining set of lifetime-related errors come about primarily due +to the interaction with function signatures. For example: + +```rust +impl Foo { + fn foo(&self, y: &u8) -> &u8 { + x + } +} +``` + +We already have work-in-progress on presenting these sorts of errors +in a better way (see [issue 42516][] for numerous examples and +details), all of which should be applicable here. In short, the name +of the game is to identify patterns and suggest changes to improve the +function signature to match the body (or at least diagnose the problem +more clearly). + +[issue 42516]: https://github.com/rust-lang/rust/issues/42516 + +Whenever possible, we should leverage points in the control-flow and +try to explain errors in "narrative" form. + +# Drawbacks +[drawbacks]: #drawbacks + +There are very few drawbacks to this proposal. The primary one is that +the **rules** for the system become more complex. However, this +permits us to accept a large number of more programs, and so we expect +that **using Rust** will feel simpler. Moreover, experience has shown +that -- for many users -- the current scheme of tying reference +lifetimes to lexical scoping is confusing and surprising. + +# Alternatives +[alternatives]: #alternatives + +### Alternative formulations of NLL + +During the runup to this RFC, a number of alternate schemes and +approaches to describing NLL were tried and discarded. + +**RFC 396.** [RFC 396][] defined lifetimes to be a "prefix" of the +dominator tree -- roughly speaking, a single-entry, multiple-exit +region of the control-flow graph. Unlike our system, this definition +did not permit gaps or holes in a lifetime. Ensuring continuous lifetimes was +meant to guarantee soundness; in this RFC, we use the liveness +constraints to achieve a similar effect. This more flexible setup +allows us to handle cases like Problem Case #3, which RFC 396 would +not have accepted. RFC 396 also did not cover dropck and a number of +other complications. + +**SSA or SSI transformation.** Rather than incorporating the "current location" into +the subtype check, we also considered formulations that first applied +an SSA transformation to the input program, and then gave each of those +variables a distinct type. This does allow some examples to type-check that +wouldn't otherwise, but it is not flexible enough for the `vec-push-ref` +example covered earlier. + +Using SSA also introduces other complications. Among other things, +Rust permits variables and temporaries to be borrowed and mutated +indirectly (e.g., via `&mut`). If we were to apply SSA to MIR in a +naive fashion, then, it would ignore these assignments when creating +numberings. For example: + +```rust +let mut x = 1; // x0, has value 1 +let mut p = &mut x; // p0 +*p += 1; +use(x); // uses `x0`, but it now has value 2 +``` + +Here, the value of `x0` changed due to a write from `p`. Thus this is +not a true SSA form. Normally, SSA transformations achieve this by +making local variables like `x` and `p` be pointers into stack slots, +and then lifting those stack slots into locals when safe. MIR was +intentionally not done using SSA form precisely to avoid the need for +such contortions (we can leave that to the optimizing backend). + +**Type per program point.** Going further than SSA, one can +accommodate `vec-push-ref` through a scheme that gives each variable a +distinct type at each point in the CFG (similar to what Ericson2314 +describes in the [stateful MIR for Rust][smr]) and applies +transformations to the lifetimes on every edge. During the rustc +design sprint, the compiler team also enumerated such a design. The +author believes this RFC to be a roughly equivalent analysis, but with +an alternative, more familiar formulation that still uses one type per +variable (rather than one type per variable per point). + +There are several advantages to the design enumerated here. For one +thing, it involves far fewer inference variables (if each variable has +many types, each of those types needs distinct inference variables at +each point) and far fewer constraints (we don't need constraints just +for connecting the type of a variable between distinct points). It is +also a more natural fit for the surface language, in which variables +have a single type. + +### Different "lifetime roles" + +In the discussion about nested method calls ([RFC 2025], and the +discussions that led up to it), there were various proposals that were +aimed at accepting the naive desugaring of a call like `vec.push(vec.len())`: + +```rust +let tmp0 = &mut vec; +let tmp1 = vec.len(); // does a shared borrow of vec +Vec::push(tmp0, tmp1); +``` + +The alternatives to RFC 2025 were focused on augmenting the type of +references to have distinct "roles" -- the most prominent such +proposal was `Ref2<'r, 'w>`, in which mutable references change to +have two distinct lifetimes, a "read" lifetime (`'r`) and a "write" +lifetime (`'w`), where read encompasses the entire span of the +reference, but write only contains those points where writes are +occuring. This RFC does not attempt to change the approach to nested +method calls, rather continuing with the RFC 2025 approach (which +affects only the borrowck handling). However, if we did wish to adopt +a `Ref2`-style approach in the future, it could be done backwards +compatibly, but it would require modifying (for example) the liveness +requirements. For example, currently, if a variable `x` is live at +some point P, then all lifetimes in the type of `x` must contain P -- +but in the `Ref2` approach, only the read lifetime would have to +contain P. This implies that lifetimes are treated differently +depending on their "role". It seems like a good idea to isolate such a +change into a distinct RFC. + +# Unresolved questions +[unresolved]: #unresolved-questions + +None at present. + +# Appendix: What this proposal will not fix + +It is worth discussing a few kinds of borrow check errors that the +current RFC will **not** eliminate. These are generally errors that +cross procedural boundaries in some form or another. + +**Closure desugaring.** The first kind of error has to do with the +closure desugaring. Right now, closures always capture local +variables, even if the closure only uses some sub-path of the variable +internally: + +```rust +let get_len = || self.vec.len(); // borrows `self`, not `self.vec` +self.vec2.push(...); // error: self is borrowed +``` + +This was discussed on [an internals thread][tc]. It is possible to fix +this [by making the closure desugaring smarter][cc]. + +[tc]: https://internals.rust-lang.org/t/borrow-the-full-stable-name-in-closures-for-ergonomics/5387 +[cc]: https://internals.rust-lang.org/t/borrow-the-full-stable-name-in-closures-for-ergonomics/5387/11?u=nikomatsakis + +**Disjoint fields across functions.** Another kind of error is when +you have one method that only uses a field `a` and another that only +uses some field `b`; right now, you can't express that, and hence +these two methods cannot be used "in parallel" with one another: + +```rust +impl Foo { + fn get_a(&self) -> &A { &self.a } + fn inc_b(&mut self) { self.b.value += 1; } + fn bar(&mut self) { + let a = self.get_a(); + self.inc_b(); // Error: self is already borrowed + use(a); + } +} +``` + +The fix for this is to refactor so as to expose the fact that the methods +operate on disjoint data. For example, one can factor out the methods into +methods on the fields themselves: + +```rust +fn bar(&mut self) { + let a = self.a.get(); + self.b.inc(); + use(a); +} +``` + +This way, when looking at `bar()` alone, we see borrows of `self.a` +and `self.b`, rather than two borrows of `self`. Another technique is +to introduce "free functions" (e.g., `get(&self.a)` and `inc(&mut +self.b)`) that expose more clearly which fields are operated upon, or +to inline the method bodies. This is a non-trivial bit of design and +is out of scope for this RFC. See +[this comment on an internals thread][cpb] for further thoughts. + +[cpb]: https://internals.rust-lang.org/t/partially-borrowed-moved-struct-types/5392/2 + +**Self-referential structs.** The final limitation we are not fixing +yet is the inability to have "self-referential structs". That is, you +cannot have a struct that stores, within itself, an arena and pointers +into that arena, and then move that struct around. This comes up in a +number of settings. There are various workarounds: sometimes you can +use a vector with indices, for example, or +[the `owning_ref` crate](https://crates.io/crates/owning_ref). The +latter, when combined with [associated type constructors][ATC], might +be an adequate solution for some uses cases, actually (it's basically +a way of modeling "existential lifetimes" in library code). For the +case of futures especially, [the `?Move` RFC][?Move] proposes another +lightweight and interesting approach. + +[?Move]: https://github.com/rust-lang/rfcs/pull/1858 + +# Endnotes + + + +**1.** Scopes always correspond to blocks with one exception: the +scope of a temporary value is sometimes the enclosing +statement. + +[RFC 396]: https://github.com/rust-lang/rfcs/pull/396 +[RFC 2025]: https://github.com/rust-lang/rfcs/pull/2025 +[smr]: https://github.com/Ericson2314/a-stateful-mir-for-rust +[10520]: https://github.com/rust-lang/rust/issues/10520 +[ATC]: https://github.com/rust-lang/rfcs/pull/1598