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 T 對 T 是 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 視為「關係朝相反方向移動」:函式對其參數的要求越低,它能處理的案例範圍就越大。
這裡有兩個例子:
假設有兩個變數,
x1和x2,其中x1的生命週期為'static,x2的生命週期為'a。那麼很明顯x1比x2更有用,因為它的存活時間更長。假設有兩個函式,
take_func1和take_func2,其中take_func1接受&'static str,take_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,因此型別不匹配
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.