深入 Rust

非詞法生命週期(non-lexical lifetimes, NLL)

早期的 Rust 有個惱人的習慣。若你借用了一個值,這個借用會被當成存活到它所在區塊的結尾——也就是它的詞法範圍——即使你在中途就不再用這個借用了。於是你可能寫出完全安全的程式碼卻被編譯器駁回,只因為一個你已用完的參考在技術上一直撐到了右大括號,與後面的某次使用相撞。程式設計師於是學會多加一層內部大括號來提早「結束」一個借用。非詞法生命週期就是這個修正:借用檢查器被升級為追蹤一個借用「實際上」被用到多久,而不是它的文字範圍跑多久。

具體來說,有了 NLL,一個借用在它「最後一次實際使用」處結束,而非在區塊結尾。想像你取一個共享參考 r = &v,透過 r 讀取,然後稍後想要一個可變參考 &mut v。在舊的詞法規則下,r 算作存活到區塊結尾,所以 &mut v 與它衝突而被駁回——即使你再也沒碰過 r。在 NLL 下,編譯器看到 r 的最後一次使用更早,從那點起便視 r 為已死,於是欣然允許 &mut v。檢查器是這樣做的:沿著控制流程圖一個區域接一個區域地算出每個參考仍被需要的確切程式點集合,並在衝突的那個借用不再被使用時,立刻准許新的借用。它更精確,而非以任何不安全的方式更寬鬆:它仍禁止每一個真的會懸置的借用。

為何重要:NLL 正是現代 Rust 感覺起來比幾年前那個語言不那麼龜毛的原因。整類「這明明沒問題,為什麼編不過」的錯誤消失了,而把程式碼包進人造範圍的權宜手法也變得不必要。誠實的提醒就在名字裡:「非詞法」描述的是這套分析,而非一個你去開啟的功能——自 2018 版起它就是預設的借用檢查器,而更新的 Polonius 檢查器把同一個想法推得更遠。你不必寫任何東西來得到 NLL;你只是受惠於借用檢查器夠聰明,能在你真的停止使用一個借用時讓它死去。

let mut v = vec![1, 2, 3]; let r = &v; // 共享借用開始 println!("{:?}", r); // r 的「最後一次」使用在此 v.push(4); // &mut v:在 NLL 下 OK(r 已死) // 在舊的詞法規則下這會是 E0502。 println!("{:?}", v); // [1, 2, 3, 4]

借用在最後一次使用處結束,而非在右大括號處——所以後面的可變借用不再與一個你已用完的參考相撞。

NLL 更精確,而非不安全:它仍駁回每一個會懸置的借用。自 2018 版起它就是預設,所以你不必去啟用它——你只是不再需要用假的內部範圍來提早結束一個借用。

又稱
NLLnon-lexical borrows非詞法借用