Rust 中的生命週期

Rust 是一種靜態類型的系統編程語言,其設計目標是快速、高效和安全。它在構建時考慮了內存安全,並提供了一些特性,可以幫助開發人員防止常見的編程錯誤,如空指針解引用、緩衝區溢出和數據競爭。生命週期是實現這些特性的關鍵概念之一。

Rust 中的生命週期是什麼?

生命週期是一種確保對數據的引用在使用期間保持有效的方法。它們在 Rust 中是一個重要的概念,因爲它們允許編譯器在引用指向的數據被釋放後檢查引用是否沒有被使用,這是 C 和 c++ 中常見的錯誤來源。

生命週期是表示數據引用與其引用的數據之間關係的一種方式。它們確保引用僅在它們所指向的數據仍然活躍時使用。

生命週期命名是指引用有效期間的名稱。它在創建引用時開始,在刪除引用時結束。通過使用 “lifetime parameter” 語法,可以在 Rust 代碼中顯式地指定生命週期。

爲什麼我們需要生命週期?

生命期很重要,因爲它們允許 Rust 編譯器檢測和防止釋放後使用的錯誤。當引用所指向的數據被釋放後再使用時,就會出現釋放後再使用的錯誤。這可能導致不可預測的行爲、崩潰甚至安全漏洞。

通過確保引用僅在引用的數據仍然有效時使用,生命週期有助於防止這些錯誤。Rust 編譯器使用生命週期提供的信息來檢查引用所指向的數據被釋放後是否沒有被使用。

生命週期是如何工作的?

生命週期使用'lifetime parameter'語法指定。生命週期形參是前面帶有 “&” 符號的名稱,用於指定引用的生命週期。

例如,考慮下面的函數,它接受兩個引用作爲輸入:

fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("x is {} and y is {}", x, y);
}

在本例中,兩個生命週期參數'a'和'b'分別指定兩個引用 x 和 y 的生命週期。Rust 編譯器使用這些信息來確保引用所指向的數據被釋放後不會被使用。

生命週期省略規則

Rust 有一組生命週期省略規則,允許編譯器在某些情況下自動推斷生命週期。這使得編寫代碼更容易,而不必顯式地爲每個引用指定生命週期。

省略規則爲:

1,如果只有一個輸入生命週期,則將它分配給所有輸出生命週期

2,如果有多個輸入生命週期,但其中一個是方法調用的'&self'或'&mut self',它將被分配給所有的輸出生命週期。

3,如果一個形參類型中只有一個生命週期,則該生命期將分配給實參類型中的所有引用。

考慮以下代碼:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {x} else {y}
}

在這個例子中,函數'longest'接受兩個對字符串的引用,並返回對較長字符串的引用。生命週期參數 “a” 用於指定引用的生命週期和返回值。由於函數只有一個生命週期形參,因此應用第一個省略規則,並將生命週期形參賦給函數中的所有引用。

代碼簡化爲:

fn longest<'a>(x: &'a str, y: &'a str) -> &str {
    if x.len() > y.len() {x} else {y}
}

再看另外一個例子:

struct Foo<'a> {
  x: &'a i32,
}

impl<'a> Foo<'a> {
  fn x(&self) -> &'a i32 {
    self.x
  }
}

在這個例子中,結構體'Foo'有一個生命期爲'a'的引用成員'x'。方法 “x” 的實現返回對相同數據的引用,因此應用第二個省略規則,該方法返回的引用的生命期爲“a”。

代碼簡化爲:

struct Foo<'a> {
  x: &'a i32,
}

impl<'a> Foo<'a> {
  fn x(&self) -> &i32 {
    self.x
  }
}

生命週期子類型

生命週期子類型是 Rust 中的一個特性,它允許生命週期相互關聯。這在引用的生命週期較長的情況下非常有用。

考慮以下代碼:

fn print_longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
    where 'b: 'a
{
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

在這個例子中,y 的生命週期是 x 的生命週期的子類型。where 子句'b:'a 指定了這種關係,即 y 的生命週期要大於等於 x 的生命週期。這意味着函數返回的引用的生命週期與 x 的生命週期相同。

生命週期子類型是有用的,因爲它允許你編寫可以接受具有不同生命週期的引用的函數,只要其中一個引用的生命週期是另一個引用的子類型。這使得編寫更加靈活和可重用的代碼成爲可能。

結構體和枚舉中的生命週期

生命週期可以在結構體和枚舉中使用,以指定作爲結構體或枚舉中引用部分的生命週期。

考慮以下代碼:

struct Foo<'a> {
    x: &'a i32,
    y: &'a i32,
}

enum Bar<'a> {
    X(i32),
    Y(&'a i32),
}

在這個例子中,結構體 Foo 有兩個生命週期爲'a 的引用 x 和 y。枚舉 Bar 有兩個變量,一個是 i32 值,另一個是生命週期爲'a 的引用。

泛型函數中的生命週期

生命週期也可以在泛型函數中使用,以指定作爲函數簽名中引用部分的生命週期。

考慮以下代碼:

fn split_at<'a>(input: &'a str, mid: usize) -> (&'a str, &'a str) {
    (&input[..mid]&input[mid..])
}

在本例中,函數 split_at 接受一個字符串的引用,並返回原始字符串的兩個子字符串的引用。生命週期'a 用於指定對輸入字符串和返回的字符串引用的生命週期。

泛型結構體和泛型枚舉中的生命週期

生命週期也可用於泛型結構體和泛型枚舉中,以指定作爲結構體或枚舉中引用部分的生命週期。

考慮下面的代碼:

struct Pair<'a, T> {
    x: &'a T,
    y: &'a T,
}

enum Either<'a, T> {
    Left(&'a T),
    Right(&'a T),
}

在這個例子中,struct Pair 有兩個生命週期爲'a 的引用 x 和 y,以及一個泛型類型參數 T。enum Either 有兩個變量,都是引用的生命週期爲'a 的 T 類型值。

Rust 中的生命週期提供了一個強大的工具,可以確保對數據的引用在使用期間保持有效。它們允許 Rust 編譯器檢測和防止使用無效的引用,使你的代碼更安全、更健壯。

生命週期還允許你編寫使用不同引用的生命週期的通用代碼,它們用於指定函數、結構體、枚舉和泛型代碼中引用的生命週期,使你的代碼更加靈活和可重用。

生命週期是一個複雜的主題,但是通過對省略規則和子類型的良好理解,並在代碼中使用它們進行一些實踐,你將能夠有效地使用生命週期在 Rust 中編寫安全和健壯的代碼。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/5nEBT7wmYlvwzVb9QrHt1w