今天我們要來介紹其他程式語言中比較少見的機制,但是在 Rust 中是屬於和參考(reference)有關的機制,那就是生命週期(lifetime)。

一、生命週期的概念

生命週期是 Rust 中的一個概念,它是一個變數的有效範圍,也就是它可以被使用的範圍。生命週期的概念是為了解決 Rust 中的參考的問題,因為參考是 Rust 中的一個重要機制,它可以讓開發者在不需要複製資料的情況下,就可以使用資料。但是參考也有一個問題,就是它的生命週期,也就是它的有效範圍,如果參考的資料已經被釋放了,那麼參考就會變成一個無效的參考,這樣就會造成程式的錯誤。

這裡有一個範例:

1
2
3
4
5
6
7
8
9
10
{
let r;

{
let x = 5;
r = &x;
}

println!("r: {}", r);
}

先說結論,這個範例是沒辦法通過編譯的,也就是會報錯。
這是因為我們先宣告了一個變數 r,然後我們在一個新的區塊中宣告了一個變數 x,並且將 x 的參考賦值給 r。
這個時候,x 的生命週期就結束了,但是 r 仍然使用 x 的參考,這樣就會造成程式的錯誤。

二、生命週期的標記

  • 生命週期的標記不會改變參考的生命週期,它只是用來標記參考的生命週期,讓 Rust 編譯器可以知道這個參考的生命週期是多少。
  • 當指定了泛型生命週期參數後,函式就可以接收帶有任何生命週期的參考。

在語法上有以下幾個重點:

  • 生命週期參數名稱:

    • 以單引號 ' 開頭
    • 一般以全部小寫字母命名
    • 大部分的慣例都使用 'a 作為生命週期參數名稱
  • 生命週期標記的位置:

    • 在參考的 & 符號之後
    • 使用空格將生命週期與參考分開

看一下以下的範例:

1
2
3
&i32        // 一個參考
&'a i32 // 一個有顯式生命週期的參考
&'a mut i32 // 一個有顯式生命週期的可變參考

單一個生命週期標記是沒有意義的,這是因為標記生命週期是為了要讓 Rust 編譯器知道多個參考的生命週期之間的關係,所以如果只有一個參考,那麼就沒有必要標記生命週期。

以下是一個範例:

1
2
3
4
5
6
7
fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s2.len() > s1.len() {
s2
} else {
s1
}
}

這個函式接收兩個參考,並且回傳一個參考,這個函式的生命週期參數名稱是 'a,這個生命週期參數名稱被用在了函式的參數與回傳值上,這樣就可以讓 Rust 編譯器知道這三個參考的生命週期是相同的。

最後在執行這個函式的時候就不會問題,並且可以正常執行:

1
2
3
4
5
6
7
8
9
fn main() {
let r;
{
let s1 = "rust";
let s2 = "ecmascript";
r = longer(s1, s2);
println!("{} is longer", r); // ecmascript is longer
}
}

三、生命週期的規則

在 Rust 中,生命週期的規則有三個:

  1. 每個參考都有一個生命週期
  2. 每個參考都有一個作用域
  3. 一個參考的生命週期不能超過它的作用域

總結

Rust 的生命週期跟所有權,兩者在其語言中的資源管理機制上都是非常重要的。由於參考是 Rust 在對於複雜類型中不可少的機制,而每個參考都有其生命週期,這是為了決定該參考是否有效的作用域。

前面有提到 Rust 的型別大多數其實都可以自動判別,而生命週期其實也一樣,都是可以自動推導出來。不過當生命週期以不同方式互相牽連的狀態下,開發者就要自行設定,這也跟型別非常複雜的狀態下,開發者就要自行設定型別一樣。

以上就是 Rust 的生命週期的基本概念,希望大家對 Rust 又更了解了一些。