生命週期(Lifetimes)#
生命週期(Lifetime) 是 Rust 所有權系統的延伸,確保參考在有效期間內都指向有效的資料。
你在 CH06 已學到借用檢查器(Borrow Checker)防止懸空參考。多數情況下編譯器能自動推斷,但當它無法確定時,就需要你用 生命週期標注 明確告訴它:「這個參考的有效範圍和另一個參考相關聯」。
1. 問題:編譯器無法推斷#
考慮一個回傳兩個字串切片中較長者的函數:
1
2
3
| fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
|
編譯器拒絕這段程式碼:
error[E0106]: missing lifetime specifier
問題在於:回傳值可能是 x 或 y 的參考,但編譯器不知道回傳的參考和哪個輸入的存活時間相關。若 x 先失效,而回傳值是 x 的參考,就會產生懸空參考。
2. 生命週期標注語法#
生命週期標注以 ' 開頭,慣用小寫字母如 'a、'b,寫在 & 之後:
&str // 一般參考
&'a str // 帶生命週期標注的參考
&'a mut str // 帶生命週期標注的可變參考
重要 :標注本身 不改變 任何參考的存活時間,只是告訴編譯器多個參考之間的生命週期 關係 。
3. 函數中的生命週期#
解決上面的問題:用 'a 說明「回傳的參考存活時間不超過 x 和 y 中較短的那個」:
1
2
3
4
5
6
7
8
9
10
11
12
13
| fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("long string");
{
let s2 = String::from("xyz");
let result = longest(s1.as_str(), s2.as_str());
println!("最長的字串是:{result}"); // OK,在 s2 有效期間內使用
}
// 不能在這裡使用 result,因為 s2 已不在作用域
}
|
'a 的實際存活時間由呼叫時 x 和 y 中 較短 的生命週期決定。
4. 生命週期省略規則#
許多常見情況不需要標注,編譯器套用 省略規則(Elision Rules) 自動推斷。
規則一 :每個輸入參考都有各自獨立的生命週期:
1
2
3
4
| fn foo(x: &str, y: &str) -> &str { ... }
// 等同於
fn foo<'a, 'b>(x: &'a str, y: &'b str) -> &??? str { ... }
// 此例仍無法推斷回傳值,需要手動標注
|
規則二 :若只有 一個 輸入參考,回傳值自動使用同一個生命週期:
1
2
3
| fn first_word(s: &str) -> &str { ... }
// 等同於(不需要手動標注)
fn first_word<'a>(s: &'a str) -> &'a str { ... }
|
規則三 :若有 &self 或 &mut self,回傳值的生命週期與 self 相同:
1
2
3
4
5
| impl MyStruct {
fn get_name(&self) -> &str { ... }
// 等同於
fn get_name<'a>(&'a self) -> &'a str { ... }
}
|
三條規則套用後若仍無法確定,編譯器才要求手動標注。
5. 結構體中的生命週期#
若結構體 持有參考 ,必須標注生命週期,確保結構體存活時間不超過所持有的參考:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("很久很久以前。還有很多故事...");
let first_sentence = novel.split('。').next().unwrap();
// excerpt 持有 first_sentence 的參考,而 first_sentence 借用自 novel
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("{}", excerpt.part);
// novel、first_sentence、excerpt 同時在作用域結束,安全
}
|
'a 告訴編譯器:ImportantExcerpt 的存活時間不能超過 part 所指向的字串資料。
結構體方法的生命週期#
1
2
3
4
5
6
7
| impl<'a> ImportantExcerpt<'a> {
// 省略規則三:回傳值與 &self 同壽,不需要手動標注
fn announce(&self, msg: &str) -> &str {
println!("公告:{msg}");
self.part
}
}
|
6. ‘static 生命週期#
'static 表示參考在 程式整個執行期間 都有效。字串字面值的型別就是 &'static str,因為它們直接嵌入在編譯後的執行檔中:
1
| let s: &'static str = "我永遠有效";
|
注意 :看到錯誤訊息建議加上 'static 時,先思考是否真的需要。通常更好的解法是修正借用關係或使用擁有所有權的型別,而非強制延長生命週期。
7. 綜合範例#
結合泛型、Trait Bound 和生命週期:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| use std::fmt::Display;
fn longest_with_msg<'a, T>(x: &'a str, y: &'a str, msg: T) -> &'a str
where
T: Display,
{
println!("訊息:{msg}");
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("long string");
let s2 = String::from("xyz");
let result = longest_with_msg(&s1, &s2, "比較中...");
println!("最長:{result}");
}
|
Reference#
https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html