non_lexical_lifetimes.md (3578B)
1 # Non-lexical lifetimes (NLL) 2 3 Non-Lexical Lifetimes (NLL) is a significant improvement to Rust's borrow 4 checker, introduced in Rust 2018 and fully stabilised by default in Rust 1.63. 5 It changed how the compiler reasons about _how long a reference lives_.[^8] 6 7 ## The Problem: Lexical Lifetimes (Before NLL) 8 9 Before NLL, a reference's lifetime lasted until the **end of its enclosing 10 scope** (the closing `}`), regardless of whether it was actually used after a 11 certain point. This caused the borrow checker to reject perfectly safe code:[^1] 12 13 ```rust 14 let mut data = vec![1, 2, 3]; 15 let first = &data[^0]; // immutable borrow starts here 16 println!("{}", first); // last actual use of `first` 17 18 // OLD borrow checker: `first` still "alive" until `}`, so this fails ❌ 19 // Even though `first` is never used again! 20 data.push(4); 21 ``` 22 23 ## The Fix: NLL 24 25 With NLL, the compiler tracks the **last point where a reference is actually 26 used**, and ends its lifetime there — not at the closing brace. This is based on 27 analysing the **control-flow graph** of your code rather than just the syntactic 28 block structure.[^6][^1] 29 30 ```rust 31 let mut data = vec![1, 2, 3]; 32 let first = &data[^0]; // immutable borrow starts 33 println!("{}", first); // ✅ last use — borrow ENDS HERE (NLL) 34 35 data.push(4); // ✅ now safe to mutate 36 ``` 37 38 ## Lexical vs. Non-Lexical Lifetimes 39 40 | | Lexical Lifetimes (Old) | Non-Lexical Lifetimes (NLL) | 41 | :----------------- | :------------------------- | :------------------------------- | 42 | Lifetime ends at | Closing `}` of scope | Last actual use of the reference | 43 | Based on | Syntax tree (scope blocks) | Control-flow graph | 44 | Rejects safe code? | Yes, in many common cases | Much less often | 45 | Introduced | Original Rust | Rust 2018, stable in Rust 1.63 | 46 47 ## NLL and Control Flow 48 49 NLL is smart enough to handle branching logic too. A borrow is only considered 50 "live" at a point if its value **could be used in the future** from that point. 51 This means in conditional branches where a reference is only used in one path, 52 it won't block mutations on the other path.[^7] 53 54 ```rust 55 let mut s = String::from("hello"); 56 57 let r = &s; 58 if some_condition { 59 println!("{r}"); // r used here in one branch 60 } 61 // NLL knows r MAY still be live here, so mutation below could still fail 62 // depending on the control flow — the compiler reasons through each path 63 ``` 64 65 In short, NLL makes Rust's borrow checker significantly more ergonomic without 66 compromising any of its safety guarantees — it simply became smarter about 67 _when_ a borrow truly ends.[^1] 68 <span style="display:none">[^10][^2][^3][^4][^5][^9]</span> 69 70 <div align="center">⁂</div> 71 72 [^1]: https://oneuptime.com/blog/post/2026-01-25-non-lexical-lifetimes-rust/view 73 74 [^2]: https://users.rust-lang.org/t/about-non-lexical-lifetimes/111614 75 76 [^3]: https://www.reddit.com/r/rust/comments/wgxr9q/nonlexical_lifetimes_nll_fully_stable_rust_blog/ 77 78 [^4]: https://stackoverflow.com/questions/50251487/what-are-non-lexical-lifetimes 79 80 [^5]: https://www.youtube.com/watch?v=XD-nc28-8Fw 81 82 [^6]: https://rust-lang.github.io/rfcs/2094-nll.html 83 84 [^7]: https://www.reddit.com/r/rust/comments/6brtsu/eli5_nonlexical_lifetimes/ 85 86 [^8]: https://blog.rust-lang.org/2022/08/05/nll-by-default.html 87 88 [^9]: https://smallcultfollowing.com/babysteps/blog/2017/02/21/non-lexical-lifetimes-using-liveness-and-location/ 89 90 [^10]: https://smallcultfollowing.com/babysteps/blog/2016/04/27/non-lexical-lifetimes-introduction/