-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
non-lexical lifetimes #2094
non-lexical lifetimes #2094
Conversation
🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 🎊 (Sorry, reactions only let you do one, this RFC deserves many) |
Verdict: let's do it! |
I have a question about a slightly modified version of an example in the RFC. Under the proposal in the RFC, would the following be valid? fn process_or_default() {
let mut map = ...;
let key = ...;
match map.get_mut(&key)
Some(value) => {
println!("{:?}", value);
map.insert(key, V::default());
},
None => {
map.insert(key, V::default());
}
}
} Note that unlike the example in the RFC, the |
If I understand correctly, in this case, lifetime of |
Disclaimer: haven't read layers 2-5 yet, and have been skimming the end of layer 1 to try to understand the earlier parts. I probably need to understand a bunch of this stuff to update the nomicon, so understanding it well is pretty important to me. First off, when describing the algorithm for solving constraints, you introduce the notion of "exiting" the lifetime Second off, I'm having trouble rectifying these statements:
So But...
So Means I think my issue might be trying to think of the lifetimes without the |
Perhaps I missed it from a quick glance, but are there mechanics that will allow opt-out from non-lexical lifetimes to plain old blocks for FFI interactions? Currently one can expect that values in the block will be deallocated in the end of the block, so it's "safe" to pass a pointer to such values to FFI code that needs to interact with these pointers in other FFI calls till the end of the block, even though Rust can't see or know whether the first FFI call just used a reference/pointer temporarily or stored somewhere else. (This is a bit vague description, I can provide an example a bit later when not from the phone, if it will be needed) |
An example I feel would be interesting, because I'm not exactly sure what the issue (but considering it's |
Not only what @xfix said, but lifetime inference cannot affect code generation. |
Ah okay, that was the confusing bit - for some reason, I thought that non-lexical lifetimes would also bring earlier destruction for objects that otherwise would live till the end of the block. |
If I understand, this code would now compile where currently the 2nd
It seemingly follows that non-lexical lifetimes limit the ability of wrapper types to impose temporary restrictions on data structures you apply them to, meaning this is not only a concern for FFIs. It'd be easy to fix this with some attribute, say |
@burdges A drop counts as an use so |
Thank you very much for discussing how error messages will be improved by this proposal! I believe that the new errors, with the borrow / invalidation / usage sites clearly highlighted, will go a long way to helping people understand what is wrong and how to fix them.
I fully agree that the usage of the term "lifetime" is overloaded, and in a clear restart we would want to avoid using it. This feels like one of the trickiest aspects of Rust for people coming from "closely related" languages where they have to think about lifetimes. When people "get" that a lifetime is actually "the time period in which the address of a variable remains constant", that's when a lot of other things start to click for them.
That said, I disagree with this aspect. This sounds like we are trying to stop using the term lifetime. I worry that such a direction will make things worse. In such a world, we'd have existing literature (blog posts, the book, Stack Overflow, etc.) that uses the term "lifetime" but the error doesn't. There'd be no obvious way for a new user to connect the two.
This is much more palatable to me. ❤️ |
I'm still a bit concerned about NLL, because to me it actually makes reasoning about references a lot harder. Specifically where a reference can be used without invalidating the program is no longer straight forward to answer. In particular the fact that introducing a use of a reference can invalidate a previously valid use of a referent feels like unfortunate error-at-a-distance behavior. That said, the benefits probably still outweigh this concern by far. |
Hello I like it, these annoying things bite me from time to time (I guess it's less often now, because I avoid the pitfalls unconsciously already). I'm not an expert in the area, and I didn't follow every argument to the ground, but I think there are two small places that could be extended a bit:
Eg, this'll compile: struct X<'a> {
val: &'a usize,
}
{
let x = 42;
let y = X { val: &x };
x = 50;
} However, it'll stop compiling if a drop implementation is added to |
fn main() -> ! {
let x: usize;
let y: &'static x = &x;
loop { }
} Actually, makes perfect sense to me based on the CFG-node contextuality of of the subtyping rules. Furthermore, this would be potentially very useful for embedded programming CC @japaric. Yes it is unsound in the presence of concurrency right now, but I'd love to delve into trying to make it work. IMO just automatically throwing it out would be like just throwing out |
Is it? |
@RalfJung well the scoped threads example below---which I missed at first :). But actually maybe not; I dislike the premise of "the guard must be dead because its destructor is never run"---I think we should have a |
This thing doesn't terminate, so I don't see a conflict with scoped threads. In fact I think I would prove this function safe in the RustBelt formal model. EDIT: I assume you mean this? Not sure what the problem is with that example; there's too much left implicit for me to follow @nikomatsakis here. Where is 'static even coming up? |
I honestly wouldn't mind myself if this example would work, however there is an issue with unwinds which can be used as alternative means of escaping a function - not in this particular example, of course, but in practical examples of this. Even a simple integer addition can unwind in Rust, which is problematic for examples more complex than just This seems safe without unwind, but having borrowck depend on whether panic is unwind or not doesn't sound like a good idea - whether code would compile would depend on global compilation options. |
I think we should take this to a separate issue---I do really like that the RFC-repo idea to avoid a mega thread. @xfix Well, I've spilled much ink on features that benefit from no-unwind-specific borrow checking https://internals.rust-lang.org/t/a-stateful-mir-for-rust/3596 so it wouldn't be the first time I crossed that bridge :D. |
The infinite loop example here: let mut foo = 22;
unsafe {
// dtor joins the thread
let _guard = thread::spawn(|foo| {
loop {
// in other thread: continuously read from `foo`
println!("{}", foo);
}
);
// we assume `_guard` is not live here, because why not?
loop {
foo += 1;
}
// unreachable drop of `_guard` joins the thread
} Is unsound because both the main and the spawned thread can access However, that API is unsafe anyway, because of let scope = Scope::new();
let mut foo = 22;
unsafe {
// dtor joins the thread
mem::forget(thread::spawn(|foo| {
loop {
// in other thread: continuously read from `foo`
println!("{}", *foo);
}
));
// there's no guard, the gates are open, the threads are spinning
loop {
foo += 1;
}
} |
@arielb1 nikomatsakis/nll-rfc#35 I forgot to link it back here. |
@Florob, what if there was a way to actively visualize the lifetime while you were editing? My original visualizer idea is somewhat invalidated by this RFC, but this seems like only a small deviation from what I was working on. Imagine the following workflow:
I imagine such a model would make it easier (not necessarily easy) for someone to learn to understand the new system. @vorner, you reminded me of an idea I've had a few times. What if we showed the user the drop statements that get injected, and point to those when explaining the issues with outlives (doesn't help with named lifetimes). Quite a few times I've gotten confused when the error description seems to point to the exact same spot, the final closing brace of the block. E.g.: struct X<'a> {
val: &'a usize,
}
{
let x = 42;
let y = X { val: &x };
// ^~ borrowed here, etc.
x = 50;
// ^~ modified here, etc.
// Some output to distinguish that this is compiler generated and not user input
drop(y);
// ^ but must live until here, etc.
// Some output to distinguish that this is compiler generated and not user input
} |
@arielb1 also raised the question of finding another name for this proposal, though I tend to think that the current name is "ok", since this isn't a feature that people will "interact with" for the most part (i.e., once its in, it'll just be "lifetimes" -- a term with which I have some issues anyhow, but that's another story!). |
Thanks a lot for the speedy clarifications. As you might have guessed, the example had gone through a little by-hand massaging, which obviously wasn't totally error-proof.. I've copied our original file at the end of this post. I think we mixed up the lvalue and rvalue for the first loan, which is why we had the idea that it gets cancelled too. Now it seems clear how we can get the error we do, with respect to the given rules. What I find a bit counter-intuitive is that the notion of loans considered in scope (with respect to the discussion in the RFC) doesn't correspond with the notion of borrows which are still active, if I understand correctly. For example, at point (2), as you've explained, the second loan gets cleared. But the borrowed reference Here's the original code and interaction with the prototype. By the way, is the information as to which loans get cancelled reported in some way? The regions reported seem to correspond to the scope of the actual borrowed references, and not the loans. struct List<+> {
value: 0,
}
let x: List<()>;
let y: List<()>;
let list: &'list mut List<()>;
let v: &'v mut ();
block START {
x = use();
y = use();
list = &'b1 mut x;
v = &'b2 mut (*list).value;
list = &'b3 mut y;
use(x);
use(v);
use(list);
}
// With use(x):
// Message: ../test/example.nll: point START/5 cannot read Var(Variable { name: "x" }) because Var(Variable { name: "x" }) is mutably borrowed (at point `START/2`)
// Loan {
// point: START/2,
// path: Var(Variable { name: "x" }),
// kind: Mut,
// region: {START/3, START/4, START/5, START/6, START/7} }
// Loan {
// point: START/3,
// path: Extension(Extension(Var(Variable { name: "list" }), FieldName { name: "*" }), FieldName { name: "value" }),
// kind: Mut,
// region: {START/4, START/5, START/6} }
// Loan {
// point: START/4,
// path: Var(Variable { name: "y" }),
// kind: Mut,
// region: {START/5, START/6, START/7} }
// If we re-run with use(x) commented out (which shifts some line numbers):
// Loan {
// point: START/2,
// path: Var(Variable { name: "x" }),
// kind: Mut,
// region: {START/3, START/4, START/5, START/6} }
// Loan {
// point: START/3,
// path: Extension(Extension(Var(Variable { name: "list" }), FieldName { name: "*" }), FieldName { name: "value" }),
// kind: Mut,
// region: {START/4, START/5} }
// Loan {
// point: START/4,
// path: Var(Variable { name: "y" }),
// kind: Mut,
// region: {START/5, START/6} }
// assert 'list == {START/3, START/4, START/5, START/6};
// assert 'v == {START/4, START/5};
// assert 'b1 == {START/3, START/4, START/5, START/6};
// assert 'b2 == {START/4, START/5};
// assert 'b3 == {START/5, START/6}; Also, as a (possibly-unrelated) side-question, I'm not sure why the region for the first loan contains |
What gets cancelled on an assignment is not loans that the overwritten variable refer to, its loans that refer to the overwritten value (and are now "orphaned"). e.g. in your code: struct List<+> {
value: 0,
}
let x: List<()>;
let y: List<()>;
let list: &'list mut List<()>;
let v: &'v mut ();
block START {
x = use();
y = use();
list = &'b1 mut x;
// This creates a lifetime constraint where `list` (and therefore the borrow
// of `x`) must live as long as `v` is.
v = &'b2 mut (*list).value;
// (*list).value is now borrowed (and "dynamically" refers to x.value), so e.g.
// use(&list); //~ ERROR cannot use `list` while `(*list).value` is borrowed
list = &'b3 mut y;
// (*list).value now refers to a completely different thing (y.value), and
// is therefore no longer borrowed. The old borrow for `(*list).value` is
// now "orphaned" - it is not accessible by any path derived from the old
// path. The lifetime constraint still remains.
// use(x); // ignore this, I don't want an error
use(&list); // non-consuming use, in any order
use(v);
use(list);
} |
Indeed, I do understand what you meant, and I too find this interaction somewhat surprising, though it seems to by and large "do the trick" (i.e., it mostly accepts the programs I want it to, and seems valid). I feel like there may be a more elegant way to express it. This is what I was trying to refer to when I wrote:
As @arielb1 points out, though, what's really happening here is that we are eliminated a kind of "false sharing" (to abuse a term). That is, we have on record a loan of One could imagine instead using a kind of SSA-like-rewriting to achieve a similar effect, at least for simple cases like local variables; but it'd be more work to do the same for paths like Anyway, I'd be not be opposed to exploring such alternate formulations, though I think we need not block on it. To my mind, what's being RFC'd here are the high-level concepts: the details of the checks we perform will be tweaked over time no doubt. (Though I would like the RFC and prototype to serve as the basis for a reference document and implementation that we can keep up to date.) |
Thanks a lot for the further details and clarifications. I'm no longer worried that there's a technical issue here - the rules indeed seem to do the "right thing". If the idea is to evolve this document into documentation later, my suggestion would be to pick a different name from "loan" if possible, for what is tracked (but maybe this terminology is fixed already). My conceptual understanding of borrowed references has always been that the references take some capabilities from each other, and that these are for a particular location, and a particular lifetime. I guess the conversion to SSA would make the distinction between the borrows and loans more explicit, since the rules for cancelling loans would be tied to distinct syntactic paths in the program, but I guess the distinction (which was what I was missing in my first comment) would still be the same in the end. Maybe converting to SSA for the sake of an example could be a(n alternative) way of explaining how the borrow checker rules can be thought about in terms of why they make sense, but I guess it would just be for illustration if so, and as you say, for general paths it might not be all that pretty. @nikomatsakis I also find it interesting to think about whether these phases could somehow feedback to each other meaningfully - it doesn't yet seem clear to me why this kind of phased checking naturally arises, but it indeed seems to achieve the right results, too! I might ponder this a bit and ping you an email if I think I have any sensible thoughts. I have a few smaller suggestions about the document, in terms of things I would find helpful to clarify. Is it best to comment here, make pull requests (but in some cases I don't actually know what I would propose yet), or add issues etc.? Or indeed, keep them to myself? :) |
If you are still looking for a name for the concept, perhaps "control flow lifetimes" would be apt? |
I'd say open PRs or open issues on the nll-rfc repo. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period is now complete. |
🔔 THIS RFC HAS BEEN MERGED! 🔔 |
This has been a long time coming! :D Congrats to Niko and the compiler team for all their hardwork on getting this right! |
so, when it will be available in stable rust ? or nightly rust to play around with? @nikomatsakis |
@0freeman00 The approved RFCs have always a link to a tracking issue that has information about the implementation. Check this out: rust-lang/rust#44928 |
The feature name is not filled which broke the RFC book a bit: https://rust-lang.github.io/rfcs/2094-nll.html (note |
…matsakis Remove migrate borrowck mode Closes rust-lang#58781 Closes rust-lang#43234 # Stabilization proposal This PR proposes the stabilization of `#![feature(nll)]` and the removal of `-Z borrowck`. Current borrow checking behavior of item bodies is currently done by first infering regions *lexically* and reporting any errors during HIR type checking. If there *are* any errors, then MIR borrowck (NLL) never occurs. If there *aren't* any errors, then MIR borrowck happens and any errors there would be reported. This PR removes the lexical region check of item bodies entirely and only uses MIR borrowck. Because MIR borrowck could never *not* be run for a compiled program, this should not break any programs. It does, however, change diagnostics significantly and allows a slightly larger set of programs to compile. Tracking issue: rust-lang#43234 RFC: https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md Version: 1.63 (2022-06-30 => beta, 2022-08-11 => stable). ## Motivation Over time, the Rust borrow checker has become "smarter" and thus allowed more programs to compile. There have been three different implementations: AST borrowck, MIR borrowck, and polonius (well, in progress). Additionally, there is the "lexical region resolver", which (roughly) solves the constraints generated through HIR typeck. It is not a full borrow checker, but does emit some errors. The AST borrowck was the original implementation of the borrow checker and was part of the initially stabilized Rust 1.0. In mid 2017, work began to implement the current MIR borrow checker and that effort ompleted by the end of 2017, for the most part. During 2018, efforts were made to migrate away from the AST borrow checker to the MIR borrow checker - eventually culminating into "migrate" mode - where HIR typeck with lexical region resolving following by MIR borrow checking - being active by default in the 2018 edition. In early 2019, migrate mode was turned on by default in the 2015 edition as well, but with MIR borrowck errors emitted as warnings. By late 2019, these warnings were upgraded to full errors. This was followed by the complete removal of the AST borrow checker. In the period since, various errors emitted by the MIR borrow checker have been improved to the point that they are mostly the same or better than those emitted by the lexical region resolver. While there do remain some degradations in errors (tracked under the [NLL-diagnostics tag](https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3ANLL-diagnostics), those are sufficiently small and rare enough that increased flexibility of MIR borrow check-only is now a worthwhile tradeoff. ## What is stabilized As said previously, this does not fundamentally change the landscape of accepted programs. However, there are a [few](https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3ANLL-fixed-by-NLL) cases where programs can compile under `feature(nll)`, but not otherwise. There are two notable patterns that are "fixed" by this stabilization. First, the `scoped_threads` feature, which is a continutation of a pre-1.0 API, can sometimes emit a [weird lifetime error](rust-lang#95527) without NLL. Second, actually seen in the standard library. In the `Extend` impl for `HashMap`, there is an implied bound of `K: 'a` that is available with NLL on but not without - this is utilized in the impl. As mentioned before, there are a large number of diagnostic differences. Most of them are better, but some are worse. None are serious or happen often enough to need to block this PR. The biggest change is the loss of error code for a number of lifetime errors in favor of more general "lifetime may not live long enough" error. While this may *seem* bad, the former error codes were just attempts to somewhat-arbitrarily bin together lifetime errors of the same type; however, on paper, they end up being roughly the same with roughly the same kinds of solutions. ## What isn't stabilized This PR does not completely remove the lexical region resolver. In the future, it may be possible to remove that (while still keeping HIR typeck) or to remove it together with HIR typeck. ## Tests Many test outputs get updated by this PR. However, there are number of tests specifically geared towards NLL under `src/test/ui/nll` ## History * On 2017-07-14, [tracking issue opened](rust-lang#43234) * On 2017-07-20, [initial empty MIR pass added](rust-lang#43271) * On 2017-08-29, [RFC opened](rust-lang/rfcs#2094) * On 2017-11-16, [Integrate MIR type-checker with NLL](rust-lang#45825) * On 2017-12-20, [NLL feature complete](rust-lang#46862) * On 2018-07-07, [Don't run AST borrowck on mir mode](rust-lang#52083) * On 2018-07-27, [Add migrate mode](rust-lang#52681) * On 2019-04-22, [Enable migrate mode on 2015 edition](rust-lang#59114) * On 2019-08-26, [Don't downgrade errors on 2015 edition](rust-lang#64221) * On 2019-08-27, [Remove AST borrowck](rust-lang#64790)
Remove migrate borrowck mode Closes #58781 Closes #43234 # Stabilization proposal This PR proposes the stabilization of `#![feature(nll)]` and the removal of `-Z borrowck`. Current borrow checking behavior of item bodies is currently done by first infering regions *lexically* and reporting any errors during HIR type checking. If there *are* any errors, then MIR borrowck (NLL) never occurs. If there *aren't* any errors, then MIR borrowck happens and any errors there would be reported. This PR removes the lexical region check of item bodies entirely and only uses MIR borrowck. Because MIR borrowck could never *not* be run for a compiled program, this should not break any programs. It does, however, change diagnostics significantly and allows a slightly larger set of programs to compile. Tracking issue: #43234 RFC: https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md Version: 1.63 (2022-06-30 => beta, 2022-08-11 => stable). ## Motivation Over time, the Rust borrow checker has become "smarter" and thus allowed more programs to compile. There have been three different implementations: AST borrowck, MIR borrowck, and polonius (well, in progress). Additionally, there is the "lexical region resolver", which (roughly) solves the constraints generated through HIR typeck. It is not a full borrow checker, but does emit some errors. The AST borrowck was the original implementation of the borrow checker and was part of the initially stabilized Rust 1.0. In mid 2017, work began to implement the current MIR borrow checker and that effort ompleted by the end of 2017, for the most part. During 2018, efforts were made to migrate away from the AST borrow checker to the MIR borrow checker - eventually culminating into "migrate" mode - where HIR typeck with lexical region resolving following by MIR borrow checking - being active by default in the 2018 edition. In early 2019, migrate mode was turned on by default in the 2015 edition as well, but with MIR borrowck errors emitted as warnings. By late 2019, these warnings were upgraded to full errors. This was followed by the complete removal of the AST borrow checker. In the period since, various errors emitted by the MIR borrow checker have been improved to the point that they are mostly the same or better than those emitted by the lexical region resolver. While there do remain some degradations in errors (tracked under the [NLL-diagnostics tag](https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3ANLL-diagnostics), those are sufficiently small and rare enough that increased flexibility of MIR borrow check-only is now a worthwhile tradeoff. ## What is stabilized As said previously, this does not fundamentally change the landscape of accepted programs. However, there are a [few](https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3ANLL-fixed-by-NLL) cases where programs can compile under `feature(nll)`, but not otherwise. There are two notable patterns that are "fixed" by this stabilization. First, the `scoped_threads` feature, which is a continutation of a pre-1.0 API, can sometimes emit a [weird lifetime error](rust-lang/rust#95527) without NLL. Second, actually seen in the standard library. In the `Extend` impl for `HashMap`, there is an implied bound of `K: 'a` that is available with NLL on but not without - this is utilized in the impl. As mentioned before, there are a large number of diagnostic differences. Most of them are better, but some are worse. None are serious or happen often enough to need to block this PR. The biggest change is the loss of error code for a number of lifetime errors in favor of more general "lifetime may not live long enough" error. While this may *seem* bad, the former error codes were just attempts to somewhat-arbitrarily bin together lifetime errors of the same type; however, on paper, they end up being roughly the same with roughly the same kinds of solutions. ## What isn't stabilized This PR does not completely remove the lexical region resolver. In the future, it may be possible to remove that (while still keeping HIR typeck) or to remove it together with HIR typeck. ## Tests Many test outputs get updated by this PR. However, there are number of tests specifically geared towards NLL under `src/test/ui/nll` ## History * On 2017-07-14, [tracking issue opened](rust-lang/rust#43234) * On 2017-07-20, [initial empty MIR pass added](rust-lang/rust#43271) * On 2017-08-29, [RFC opened](rust-lang/rfcs#2094) * On 2017-11-16, [Integrate MIR type-checker with NLL](rust-lang/rust#45825) * On 2017-12-20, [NLL feature complete](rust-lang/rust#46862) * On 2018-07-07, [Don't run AST borrowck on mir mode](rust-lang/rust#52083) * On 2018-07-27, [Add migrate mode](rust-lang/rust#52681) * On 2019-04-22, [Enable migrate mode on 2015 edition](rust-lang/rust#59114) * On 2019-08-26, [Don't downgrade errors on 2015 edition](rust-lang/rust#64221) * On 2019-08-27, [Remove AST borrowck](rust-lang/rust#64790)
Remove migrate borrowck mode Closes #58781 Closes #43234 # Stabilization proposal This PR proposes the stabilization of `#![feature(nll)]` and the removal of `-Z borrowck`. Current borrow checking behavior of item bodies is currently done by first infering regions *lexically* and reporting any errors during HIR type checking. If there *are* any errors, then MIR borrowck (NLL) never occurs. If there *aren't* any errors, then MIR borrowck happens and any errors there would be reported. This PR removes the lexical region check of item bodies entirely and only uses MIR borrowck. Because MIR borrowck could never *not* be run for a compiled program, this should not break any programs. It does, however, change diagnostics significantly and allows a slightly larger set of programs to compile. Tracking issue: #43234 RFC: https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md Version: 1.63 (2022-06-30 => beta, 2022-08-11 => stable). ## Motivation Over time, the Rust borrow checker has become "smarter" and thus allowed more programs to compile. There have been three different implementations: AST borrowck, MIR borrowck, and polonius (well, in progress). Additionally, there is the "lexical region resolver", which (roughly) solves the constraints generated through HIR typeck. It is not a full borrow checker, but does emit some errors. The AST borrowck was the original implementation of the borrow checker and was part of the initially stabilized Rust 1.0. In mid 2017, work began to implement the current MIR borrow checker and that effort ompleted by the end of 2017, for the most part. During 2018, efforts were made to migrate away from the AST borrow checker to the MIR borrow checker - eventually culminating into "migrate" mode - where HIR typeck with lexical region resolving following by MIR borrow checking - being active by default in the 2018 edition. In early 2019, migrate mode was turned on by default in the 2015 edition as well, but with MIR borrowck errors emitted as warnings. By late 2019, these warnings were upgraded to full errors. This was followed by the complete removal of the AST borrow checker. In the period since, various errors emitted by the MIR borrow checker have been improved to the point that they are mostly the same or better than those emitted by the lexical region resolver. While there do remain some degradations in errors (tracked under the [NLL-diagnostics tag](https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3ANLL-diagnostics), those are sufficiently small and rare enough that increased flexibility of MIR borrow check-only is now a worthwhile tradeoff. ## What is stabilized As said previously, this does not fundamentally change the landscape of accepted programs. However, there are a [few](https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3ANLL-fixed-by-NLL) cases where programs can compile under `feature(nll)`, but not otherwise. There are two notable patterns that are "fixed" by this stabilization. First, the `scoped_threads` feature, which is a continutation of a pre-1.0 API, can sometimes emit a [weird lifetime error](rust-lang/rust#95527) without NLL. Second, actually seen in the standard library. In the `Extend` impl for `HashMap`, there is an implied bound of `K: 'a` that is available with NLL on but not without - this is utilized in the impl. As mentioned before, there are a large number of diagnostic differences. Most of them are better, but some are worse. None are serious or happen often enough to need to block this PR. The biggest change is the loss of error code for a number of lifetime errors in favor of more general "lifetime may not live long enough" error. While this may *seem* bad, the former error codes were just attempts to somewhat-arbitrarily bin together lifetime errors of the same type; however, on paper, they end up being roughly the same with roughly the same kinds of solutions. ## What isn't stabilized This PR does not completely remove the lexical region resolver. In the future, it may be possible to remove that (while still keeping HIR typeck) or to remove it together with HIR typeck. ## Tests Many test outputs get updated by this PR. However, there are number of tests specifically geared towards NLL under `src/test/ui/nll` ## History * On 2017-07-14, [tracking issue opened](rust-lang/rust#43234) * On 2017-07-20, [initial empty MIR pass added](rust-lang/rust#43271) * On 2017-08-29, [RFC opened](rust-lang/rfcs#2094) * On 2017-11-16, [Integrate MIR type-checker with NLL](rust-lang/rust#45825) * On 2017-12-20, [NLL feature complete](rust-lang/rust#46862) * On 2018-07-07, [Don't run AST borrowck on mir mode](rust-lang/rust#52083) * On 2018-07-27, [Add migrate mode](rust-lang/rust#52681) * On 2019-04-22, [Enable migrate mode on 2015 edition](rust-lang/rust#59114) * On 2019-08-26, [Don't downgrade errors on 2015 edition](rust-lang/rust#64221) * On 2019-08-27, [Remove AST borrowck](rust-lang/rust#64790)
Remove migrate borrowck mode Closes #58781 Closes #43234 # Stabilization proposal This PR proposes the stabilization of `#![feature(nll)]` and the removal of `-Z borrowck`. Current borrow checking behavior of item bodies is currently done by first infering regions *lexically* and reporting any errors during HIR type checking. If there *are* any errors, then MIR borrowck (NLL) never occurs. If there *aren't* any errors, then MIR borrowck happens and any errors there would be reported. This PR removes the lexical region check of item bodies entirely and only uses MIR borrowck. Because MIR borrowck could never *not* be run for a compiled program, this should not break any programs. It does, however, change diagnostics significantly and allows a slightly larger set of programs to compile. Tracking issue: #43234 RFC: https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md Version: 1.63 (2022-06-30 => beta, 2022-08-11 => stable). ## Motivation Over time, the Rust borrow checker has become "smarter" and thus allowed more programs to compile. There have been three different implementations: AST borrowck, MIR borrowck, and polonius (well, in progress). Additionally, there is the "lexical region resolver", which (roughly) solves the constraints generated through HIR typeck. It is not a full borrow checker, but does emit some errors. The AST borrowck was the original implementation of the borrow checker and was part of the initially stabilized Rust 1.0. In mid 2017, work began to implement the current MIR borrow checker and that effort ompleted by the end of 2017, for the most part. During 2018, efforts were made to migrate away from the AST borrow checker to the MIR borrow checker - eventually culminating into "migrate" mode - where HIR typeck with lexical region resolving following by MIR borrow checking - being active by default in the 2018 edition. In early 2019, migrate mode was turned on by default in the 2015 edition as well, but with MIR borrowck errors emitted as warnings. By late 2019, these warnings were upgraded to full errors. This was followed by the complete removal of the AST borrow checker. In the period since, various errors emitted by the MIR borrow checker have been improved to the point that they are mostly the same or better than those emitted by the lexical region resolver. While there do remain some degradations in errors (tracked under the [NLL-diagnostics tag](https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3ANLL-diagnostics), those are sufficiently small and rare enough that increased flexibility of MIR borrow check-only is now a worthwhile tradeoff. ## What is stabilized As said previously, this does not fundamentally change the landscape of accepted programs. However, there are a [few](https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3ANLL-fixed-by-NLL) cases where programs can compile under `feature(nll)`, but not otherwise. There are two notable patterns that are "fixed" by this stabilization. First, the `scoped_threads` feature, which is a continutation of a pre-1.0 API, can sometimes emit a [weird lifetime error](rust-lang/rust#95527) without NLL. Second, actually seen in the standard library. In the `Extend` impl for `HashMap`, there is an implied bound of `K: 'a` that is available with NLL on but not without - this is utilized in the impl. As mentioned before, there are a large number of diagnostic differences. Most of them are better, but some are worse. None are serious or happen often enough to need to block this PR. The biggest change is the loss of error code for a number of lifetime errors in favor of more general "lifetime may not live long enough" error. While this may *seem* bad, the former error codes were just attempts to somewhat-arbitrarily bin together lifetime errors of the same type; however, on paper, they end up being roughly the same with roughly the same kinds of solutions. ## What isn't stabilized This PR does not completely remove the lexical region resolver. In the future, it may be possible to remove that (while still keeping HIR typeck) or to remove it together with HIR typeck. ## Tests Many test outputs get updated by this PR. However, there are number of tests specifically geared towards NLL under `src/test/ui/nll` ## History * On 2017-07-14, [tracking issue opened](rust-lang/rust#43234) * On 2017-07-20, [initial empty MIR pass added](rust-lang/rust#43271) * On 2017-08-29, [RFC opened](rust-lang/rfcs#2094) * On 2017-11-16, [Integrate MIR type-checker with NLL](rust-lang/rust#45825) * On 2017-12-20, [NLL feature complete](rust-lang/rust#46862) * On 2018-07-07, [Don't run AST borrowck on mir mode](rust-lang/rust#52083) * On 2018-07-27, [Add migrate mode](rust-lang/rust#52681) * On 2019-04-22, [Enable migrate mode on 2015 edition](rust-lang/rust#59114) * On 2019-08-26, [Don't downgrade errors on 2015 edition](rust-lang/rust#64221) * On 2019-08-27, [Remove AST borrowck](rust-lang/rust#64790)
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 requires 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.)
Due to its size and complexity, this RFC is being run through an experimental process. The text of the RFC itself is not in this file -- rather, it can be found at the non-lexical lifetimes repository. Prior to merging, the final version of the text will be added to this PR directly; until then, hosting the RFC at the repository allows for us to track (using open issues or pending PRs) important points of conversation and so forth. Feel free to leave ordinary comments on the PR, or to open issues -- important points will be elevated to issues for further discussion.
The ideas in this RFC have been implemented in prototype form. 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.
Rendered.