生命週期
生命週期(lifetime)是編譯器用來確保「參照永遠有效」的機制。多數時候編譯器能自動推斷,你不會看到它;只有當編譯器無法判斷參照之間的關係時,才需要你用 'a 這樣的標註說明。本頁說明它解決什麼問題,以及何時需要手動標註。
生命週期解決什麼問題
每個參照都有一段「有效範圍」。如果參照比它指向的值活得更久,就會變成懸空參照。編譯器會比對兩者的範圍:
錯誤訊息是 x does not live long enough。r 想在大括號外使用,但 x 已經不在了。
函式中的生命週期標註
當函式回傳參照時,編譯器需要知道回傳的參照來自哪一個參數。下面的函式回傳兩個字串中較長的一個,但編譯器無法判斷回傳值是 x 還是 y:
// ❌ 無法編譯:缺少生命週期標註
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
fn main() {
println!("{}", longest("abc", "de"));
}
加上生命週期參數 'a 後,就是在告訴編譯器:「回傳值的有效期間,與兩個參數中較短的那一個相同。」
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let a = String::from("long string");
let result;
{
let b = String::from("xyz");
result = longest(a.as_str(), b.as_str());
println!("{result}"); // 在 b 還活著的範圍內使用,沒問題
}
}
標註不會改變壽命
生命週期標註只是描述參照之間的關係,讓編譯器能檢查,它不會延長或縮短任何值的壽命。
結構體中的參照
結構體若要存放參照,欄位也需要標註,表示結構體的實例不能比它所參照的資料活得更久:
struct Excerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = Excerpt { part: first_sentence };
println!("{}", excerpt.part);
}
自動推斷(省略規則)
大多數函式不用寫標註,是因為編譯器套用三條「生命週期省略規則」:
- 每個參照參數各自獲得一個生命週期。
- 只有一個輸入生命週期時,回傳值的參照就使用它。
- 若有
&self或&mut self,回傳值的參照使用self的生命週期。
所以下面的函式不需要標註:
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
fn main() {
println!("{}", first_word("hello world"));
}
'static
'static 表示這個參照在整個程式執行期間都有效。字串字面值都是 &'static str,因為它們直接存在執行檔中:
不要為了消除錯誤就亂加 'static
遇到生命週期錯誤時,多半是設計上有問題。先想想:這個結構體真的需要存參照嗎?改成擁有資料(例如用 String 取代 &str)通常更簡單,只是多一次複製。
實用心法
- 初學階段,遇到生命週期錯誤,優先考慮「回傳擁有的值」或「使用
String/.clone()」。 - 函式只回傳參照時,先確認它來自哪個參數,再標註。
- 大多數程式碼根本不用寫生命週期,不必一開始就過度擔心。
推薦影音
生命週期入門
簡述:Let's Get Rusty 依照官方書籍第 10 章製作,專門講解生命週期標註的寫法與原因,適合第一次遇到 'a 的人。
進階:Crust of Rust 生命週期標註
簡述:Jon Gjengset 以直播方式逐步實作範例,探討生命週期的細節與常見陷阱,長度約兩小時,適合已有基礎、想深入理解的人。
所有權的三個核心觀念到此完成。接下來用它們來設計資料:資料建模。