How Rust 2018+ computes live ranges based on Control Flow Graphs (CFG) rather than rigid lexical scope blocks.
fn process_map(map: &mut std::collections::HashMap<String, i32>) {
let key = String::from("score");
// In Rust 2015, 'val' borrow extended to the end of the function!
if let Some(val) = map.get(&key) {
println!("Existing score: {}", val);
} // Under NLL, immutable borrow of 'map' ends right here!
// We can now immediately mutate 'map' without compiler error
map.insert(key, 100);
}Early Rust versions tied the lifetime of every reference strictly to the syntactic scope (`{ ... }`) where it was declared.
Non-Lexical Lifetimes (NLL) revolutionized Rust borrow checking by analyzing the control flow graph. A borrow is considered live only at the exact CFG points where it can actually be dereferenced in future execution.
If a reference is never read again after line 12, its loan is released at line 12, freeing the underlying variable for new exclusive borrows at line 13.
NLL represents variables as sets of Mid-Level Intermediate Representation (MIR) control points where values are live.
let mut x = 10;
let r = &x; // Point A: Loan starts
println!("{}", r); // Point B: Last read of r! Loan ends here.
x = 20; // Point C: Valid mutation under NLL (Error in 2015).