生命週期(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

問題在於:回傳值可能是 xy 的參考,但編譯器不知道回傳的參考和哪個輸入的存活時間相關。若 x 先失效,而回傳值是 x 的參考,就會產生懸空參考。


2. 生命週期標注語法#

生命週期標注以 ' 開頭,慣用小寫字母如 'a'b,寫在 & 之後:

&str        // 一般參考
&'a str     // 帶生命週期標注的參考
&'a mut str // 帶生命週期標注的可變參考

重要 :標注本身 不改變 任何參考的存活時間,只是告訴編譯器多個參考之間的生命週期 關係


3. 函數中的生命週期#

解決上面的問題:用 'a 說明「回傳的參考存活時間不超過 xy 中較短的那個」:

 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 的實際存活時間由呼叫時 xy較短 的生命週期決定。


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