生命週期(Lifetime)
在前面的「所有權(Ownership)」章節曾經介紹 Rust 是如何透過所有權的轉讓與出借,來決定是否要釋放不再使用的記憶體空間,但其實在介紹所有權的時候,我們有省略了一些東西。
Rust 有「借(Borrow)」的設計,不需要複製整份資料,只要用 &
做個參照或切片,就能透過參照直接取用原本的資料。但是想像一下,如果我做了一個 Slice 指向某個 Vector,萬一那個 Vector 因為某些因素被放掉了,那麼這個 Slice 會參照到什麼東西?原本指向的記憶體位置讓出來之後假設運氣好沒有被其它資料佔用的話,也許有機會拿回來,但萬一被其它資料佔用了呢?Rust 編譯器為了確保程式可以正確執行,所以不會允許這種懸空參照(dangling reference)可能造成的不確定性,在編譯階段就會直接被擋下來。
所以如果要正常運作的話,被指到的 Vector 就不能比參照它的 Slice 還早消失對吧。Rust 的編譯器有一個 Borrow Checker(以下簡稱 BC)的設計,它會檢查參照的生命週期,如果 Rust 編譯器發現參照的資料的生命週期比較短,也就是會比較早消失,編譯就不會通過。雖然 BC 的檢查可能會有點囉嗦甚至難理解,但它可以避免常見的像是「使用後釋放(use-after-free)」或「雙重釋放(double-free)」的記憶體問題,也是它被說是比較「安全」的原因。
我們先來看一段程式碼:
fn print_age() {
let age = 12;
let my_age = &age;
println!("{}", my_age); // 印出 12
}
這裡我故意把程式碼寫的開一點,因為待會要畫個圖會看起來比較清楚一些。上面這段程式碼應該很簡單,最後結果就是印出 12。這裡需要特別注意的是 &age
的寫法,在 Rust 並不是只有陣列、字串之類的資料結構才能借,即使是 primitive 的整數型別也可以借。其中 age
跟 my_age
這兩個變數,都會在 print_age()
函數執行結束之後就會放掉他們所佔用的資源。
所謂的「生命週期」就是指從什麼開始出生到什麼時候結束,假設我們現在以 BC 的角度來看待這段程式碼,在 BC 眼裡看來變數 age
的生命週期就是從第 2 行的 let
開始到整個函數結束為止,這裡我用 'age
標記它,而 my_age
也一樣,它的生命週期就是從 let my_age
那行開始,這裡我用 'my_age
標記它:
fn print_age() {
let age = 12; //-----------------+- 'age
// |
let my_age = &age; //-----+- 'my_age |
// | |
println!("{}", my_age); // | |
//-----+ |
} //-----------------+
my_age
的生命週期比較短,因為它在 println!()
之後就沒用到了,Rust 會在這裡就把它 drop 掉;而 age
變數的生命週期稍微長一點,但也活不過這個函數。就是因為 age
的生命週期比較長,所以當 my_age
想要去借的時候,BC 會先檢查這樣會不會發生提早消失的情況,如果不會發生,Rust 的編譯器就不會抱怨了。
我們再來看另外的例子:
fn print_age() {
let my_age;
{
let age = 12;
my_age = &age;
}
println!("{}", my_age);
}
在 Rust 裡這樣突然加上大括號是 ok 的,不會造成語法錯誤。其實在 JavaScript 這樣寫也不會出錯,只是會這樣寫的人可能比較少。不管在 JavaScript 或 Rust 都一樣,透過 { }
這樣的大括號的寫法會做出一個的 Scope 出來,在這個 Scope 裡的東西宣告的變數在這個 Block 結束之後就會消失(但在 JavaScript 裡的 var
不是這麼一回事)。不同的是,在 Rust 裡還有參照這回事,所以故事就會不太一樣了。我用一樣的手法把生命週期標記出來:
fn print_age() {
let my_age; //---------------+-- 'my_age
// |
{ // |
let age = 12; //------+ 'age |
my_age = &age; // | |
} //------+ |
// |
println!("{}", my_age); // |
} //---------------+
在這裡 my_age
想要去參照 age
,但 BC 發現 age
的生命週期在大括號結束之後就跟著結束了,Rust 不允許這種白髮人送黑髮人的事情發生,所以在編譯階段就會直接被擋下來。