non-lexical lifetimes
Early Rust had an annoying habit. If you borrowed a value, the borrow was treated as alive until the end of its enclosing block — its lexical scope — even if you stopped using the borrow halfway through. So you could write perfectly safe code that the compiler rejected, just because a reference you were finished with technically lasted until the closing brace and collided with a later use. Programmers learned to add extra inner braces to 'end' a borrow early. Non-lexical lifetimes are the fix: the borrow checker was upgraded to track how long a borrow is ACTUALLY used, not how long its textual scope runs.
Concretely, with NLL a borrow ends at its last actual use, not at the end of the block. Picture taking a shared reference r = &v, reading through r, and then later wanting a mutable reference &mut v. Under the old lexical rule, r counted as live until the end of the block, so the &mut v conflicted with it and was rejected — even though you never touched r again. Under NLL the compiler sees that r's last use was earlier, considers r dead from that point, and happily allows the &mut v. The checker does this by computing, region by region through the control-flow graph, the exact set of program points where each reference is still needed, and it permits a new borrow as soon as the conflicting one is no longer used. It is more precise, not more permissive in any unsafe way: it still forbids every borrow that could actually dangle.
Why this matters: NLL is the reason modern Rust feels far less fussy than the language of a few years ago. Whole categories of 'why won't this compile, it's obviously fine' errors disappeared, and the workaround of wrapping code in artificial scopes became unnecessary. The honest caveat is in the name: 'non-lexical' describes the analysis, not a feature you turn on — it has been the default borrow checker since the 2018 edition, and the newer Polonius checker pushes the same idea further. You will not write anything to get NLL; you simply benefit from the borrow checker being smart enough to let a borrow die when you actually stop using it.
let mut v = vec![1, 2, 3]; let r = &v; // shared borrow starts println!("{:?}", r); // r's LAST use is here v.push(4); // &mut v: OK under NLL (r already dead) // Under old lexical rules this was E0502. println!("{:?}", v); // [1, 2, 3, 4]
A borrow ends at its last use, not at the closing brace — so a later mutable borrow no longer collides with a reference you are done with.
NLL is more precise, not unsafe: it still rejects every borrow that could dangle. It is the default since the 2018 edition, so you do not enable it — you just no longer need fake inner scopes to end a borrow early.