1.12.1. 生命週期變異性

變異性是 Rust 型別系統中的概念。它描述了泛型參數——特別是生命週期參數——在型別層級結構中彼此之間的關係。

我們可以簡單地將它視為變異性描述了哪些型別是其他型別的「子型別」,其中「子型別」與 Java 和 C# 中使用的概念有些相似。

此外,變異性也關心「子型別」何時可以替換「父型別」,反之亦然

一般來說,如果 A 是 B 的子型別,那麼 A 至少與 B 一樣有用。這裡有一個 Rust 的例子:如果一個函式接受 &'a str,那麼可以傳入 &'static str因為 'static'a 的子型別,'static 的存活時間至少與任何 'a 一樣長(而且 'static 可以對整個程式保持有效)。

1.12.2. 三種生命週期變異性

所有型別都有變異性。每個型別所關聯的變異性,定義了哪些相似型別可以用於該型別的位置。

注意:以下內容相當困難。建議您先回想高中數學中充分條件與必要條件的概念。

1. Covariant

Covariant 表示型別只能被「子型別」替換。

Covariance 意味著:

if A <: B (A is a subtype of B), then F<A> <: F<B> (F<A> is also a subtype of F<B>)

Enter fullscreen mode Exit fullscreen mode

這是一種由小到大的傳遞繼承關係,類似於從充分條件進行推理:如果 A 成立,那麼 B 也必定成立(A 是 B 的充分條件)。

例如,&'static T 可以替換 &'a T,因為 &T 對生命週期 'a 是 covariant 的,因此 'a 可以被其子型別之一(如 'static)替換。

2. Invariant

Invariant 表示您必須提供完全指定的型別。

Invariance 意味著:

A <: B cannot imply F<A> <: F<B>, and F<B> <: F<A> also cannot be inferred

Enter fullscreen mode Exit fullscreen mode

這意味著 F<A>F<B> 之間沒有足夠的關係可以從另一方推導,因此它們既不是充分條件也不是必要條件;它們是獨立的。

例如,可變參考 &mut TT 是 invariant 的。

3. Contravariant

Contravariance 意味著:

if A <: B (A is a subtype of B), then F<B> <: F<A> (F<B> is instead a subtype of F<A>)

Enter fullscreen mode Exit fullscreen mode

這裡的邏輯是:「為了使 F<A> 成立,B 必須滿足 A 的條件」,這更像是必要條件如果 B 成立,那麼 A 也必須成立(A 是 B 的必要條件)。

您可以將 contravariance 視為「關係朝相反方向移動」:函式對其參數的要求越低,它能處理的案例範圍就越大。

這裡有兩個例子:

  • 假設有兩個變數,x1x2,其中 x1 的生命週期為 'staticx2 的生命週期為 'a。那麼很明顯 x1x2 更有用,因為它的存活時間更長。

  • 假設有兩個函式,take_func1take_func2,其中 take_func1 接受 &'static strtake_func2 接受 &'a str。很明顯,take_func1 對其參數的要求更嚴格,這意味著 take_func1 的用途不如 take_func2 廣泛。

從上面的兩個例子我們可以看到,給變數更長的生命週期會使其更有用,但要求函式參數具有更長的生命週期則會使函式用途較少。這就是 contravariance。

那麼什麼對什麼是 contravariant 呢?它是函式對其參數型別的 contravariance

1.12.3. 生命週期變異性的作用

讓我們看一個例子,了解生命週期變異性有什麼作用:

struct MutStr<'a, 'b> {  
    s: &'a mut &'b str,  
}  

fn main() {  
    let mut s = "hello";  
    *MutStr { s: &mut s }.s = "world";  
    println!("{}", s);  
}

Enter fullscreen mode Exit fullscreen mode

這段程式碼令人困惑的部分在於 MutStr 結構體,讓我們來拆解它:

  • 該結構體只有一個欄位,但有兩個生命週期
  • &'a mut 表示可變參考,且該可變參考的生命週期為 'a
  • &'b str 表示指向字串切片的參考,且該字串切片的生命週期為 'b
  • 換句話說,MutStr 允許您儲存一個可變參考,該參考指向指向字串切片的參考。您可以修改 s 本身,但無法修改 &'b str 所指向的字串內容

接下來,讓我們看看 main 中的邏輯:

  • let mut s = "hello"; 宣告變數 s,其型別為 &str,值為 "hello"

  • *MutStr { s: &mut s }.s = "world"; 實際上是將多個步驟合併為一行。讓我們將它們分開:

    • MutStr { s: &mut s } 將指向 s 的可變參考傳入 MutStr 結構體。此時,MutStr 內部 s 欄位的值為 "hello"
    • *MutStr { s: &mut s }.s = "world"; 中,.s 表示存取 s 欄位(此時欄位的值為 &mut s)。* 解參考 s,也就是取得指向字串切片 s 的參考本身。= "world" 改變所指向的值——s 原本指向 "hello",現在被改為 "world",也就是 s = "world"

如果只有一個生命週期——這段程式碼還能這樣寫嗎?

struct MutStr<'a> {  
    s: &'a mut str,  
}  

fn main() {  
    let mut s = "hello";  
    *MutStr { s: &mut s }.s = "world";  
    println!("{}", s);  
}

Enter fullscreen mode Exit fullscreen mode

輸出:

error[E0308]: mismatched types
 --> src/main.rs:7:31
  |
7 |     *MutStr { s: &mut s }.s = "world";  
  |     -----------------------   ^^^^^^^ expected `str`, found `&str`
  |     |
  |     expected due to the type of this binding

error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> src/main.rs:7:5
  |
7 |     *MutStr { s: &mut s }.s = "world";  
  |     ^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `str`
  = note: the left-hand-side of an assignment must have a statically known size

Enter fullscreen mode Exit fullscreen mode

在這一行程式碼上,rustc 在指派處報告失敗(expected str, found &str,以及 str 是 unsized)。更深層的型別問題是 &mut s 的型別為 &mut &str,而欄位預期的是 &mut str

更具體地說:

  • 變數 s 的型別為 &str(指向字串切片的參考)。
  • 當您寫下 &mut s 時,其實際型別為 &mut &str,也就是指向變數 s 的可變參考。然而,MutStr 的定義要求欄位 s 的型別為 &mut str
  • 如果將建構分離為 MutStr { s: &mut s },rustc 會報告您無法將 & 參考後面的資料作為可變借用——這是同一個根本不匹配,只是以不同的方式呈現。

這些是不同的參照物型別,而不是生命週期子型別的問題。(另外,請注意 &mut T 確實 支援 unsizing coercions,例如 &mut [T; N]&mut [T];該機制仍然無法將 &mut &str 轉換為 &mut str。)

變異性確實重要的地方在於雙生命週期版本:&'a mut &'b str'b 是 invariant 的,這可以防止透過可變參考指派時,不安全地縮短內部借用。

您也可以這樣思考:

  • 字串字面值("hello""world" 都是字串字面值)的型別為 &str,並且有隱含的 'static 生命週期註記,這意味著 &str 實際上是 &'static str。在原始結構體中,這對應於 'b
  • 結構體的 'a 對應於可變參考的生命週期,也就是 *MutStr { s: &mut s }.s = "world" 這一行中 &mut 可變參考的生命週期
  • 變更後,只有單一生命週期參數的結構體預期的是 &mut str,但 &mut s 的型別仍然是 &mut &str,因此型別不匹配