1.12.1. ライフタイムの変性

変性は Rust の型システムにおける概念です。これは、ジェネリックパラメータ — 特にライフタイムパラメータ — が型階層の中でどのように互いに関連するかを説明します。

簡潔に言うと、変性は、ある型が他の型の「部分型」であるかどうかを表すものです。ここでの「部分型」は Java や C# で使われる概念と似ています。

また、「部分型」が「上位型」を置き換えられる条件やその逆も扱います

一般に、A が B の部分型である場合、A は少なくとも B と同等に役立ちます。Rust の例として、関数が &'a str を受け取るなら、&'static str を渡せます。なぜなら 'static'a の部分型であり、'static'a と同じかそれ以上生存するためです('static はプログラム全体で有効な場合もあります)。

1.12.2. ライフタイムの変性の 3 種類

すべての型には変性があります。各型に関連付けられた変性は、その型の位置で使用できる類似の型を定義します。

注: 以下の内容はかなり難しいです。まず高校数学の十分条件と必要条件の考え方を思い出すことをおすすめします。

1. 共変

共変とは、型が「部分型」のみで置き換えられることを意味します。

共変の意味:

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 に対して共変だからです。したがって 'a はその部分型、例えば 'static に置き換えられます。

2. 不変

不変とは、指定された型そのものを提供しなければならないことを意味します。

不変の意味:

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 に対して不変です。

3. 反変

反変の意味:

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 の必要条件)。

反変は「関係が逆方向に働く」と考えることができます。関数がパラメータに対して要求する条件が緩いほど、より広い範囲のケースを扱えます。

以下に 2 つの例を示します:

  • x1x2 の 2 つの変数があり、x1 はライフタイム 'staticx2 はライフタイム 'a を持つとします。明らかに x1 のほうが x2 より有用です。なぜならより長く生存するからです。

  • take_func1take_func2 の 2 つの関数があり、take_func1&'static str を受け取り、take_func2&'a str を受け取るとします。明らかに take_func1 のほうが引数に対する要求が厳しく、take_func2 より広く有用ではありません。

上記の 2 つの例から、変数に長いライフタイムを与えるとより有用になりますが、関数のパラメータに長いライフタイムを要求すると関数は有用でなくなります。それが反変です。

では何が何に対して反変なのでしょうか。それは関数の引数の型に対する反変性です。

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 構造体です。分解してみましょう:

  • この構造体にはフィールドが 1 つしかありませんが、ライフタイムが 2 つあります
  • &'a mut可変参照を意味し、その可変参照のライフタイムは 'a です
  • &'b str文字列スライスへの参照を意味し、その文字列スライスのライフタイムは 'b です
  • つまり MutStr を使うと、文字列スライスへの参照を指す可変参照を保存できます。s 自体は変更できますが、&'b str が指す文字列の内容は変更できません

次に main 内のロジックを見てみましょう:

  • let mut s = "hello"; は変数 s を宣言します。その型は &str で、値は "hello" です

  • *MutStr { s: &mut s }.s = "world"; は実際には 1 行にまとめられた複数のステップです。分解してみましょう:

    • MutStr { s: &mut s }s への可変参照を MutStr 構造体に渡します。この時点で MutStr 内の s フィールドの値は "hello" です
    • *MutStr { s: &mut s }.s = "world"; では .ss フィールドへのアクセスを意味します(この時点でフィールドの値は &mut s)。*s参照外しし、文字列スライス s への参照自体を取得します。= "world" は参照先の値を変更します — s は以前 "hello" を指していましたが、今は "world" に変更されます。つまり s = "world" です

もしライフタイムが 1 つだけだったら、このコードは書けるでしょうか?

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

この 1 行のコードで、rustc は代入部分でエラーを報告します(expected str, found &str、および str のサイズが不明)。より深い型の問題は、&mut s の型が &mut &str であるのに対し、フィールドが &mut str を期待している点です。

より具体的に:

  • 変数 s の型は &str(文字列スライスへの参照)です。
  • &mut s を書くと、その実際の型は &mut &str、つまり変数 s への可変参照になります。しかし MutStr の定義ではフィールド s の型が &mut str である必要があります。
  • MutStr { s: &mut s } のように構築を分離して書くと、rustc は & 参照の背後のデータを可変で借用できないと報告します。これは同じ根本的な不一致が別の形で表面化したものです。

これらは参照先の型の違いであり、ライフタイムの部分型に関する問題ではありません。(別途、&mut T&mut [T; N]&mut [T] のような unsizing coercion をサポートしますが、この仕組みでも &mut &str&mut str に変換することはできません。)

不変性が実際に重要になるのは、2 つのライフタイムを持つバージョンです。&'a mut &'b str'b に対して不変であり、可変参照を通じて代入する際に内部の借用を不健全に短縮することを防ぎます。

次のように考えることもできます:

  • 文字列リテラル("hello""world" は文字列リテラル)は型 &str と暗黙の 'static ライフタイム注釈を持ちます。つまり &str は実際には &'static str です。元の構造体ではこれが 'b に対応します
  • 構造体の 'a は可変参照のライフタイムに対応し、*MutStr { s: &mut s }.s = "world" の行における &mut 可変参照のライフタイムです
  • 変更後、ライフタイムパラメータが 1 つだけの構造体は &mut str を期待しますが、&mut s は依然として &mut &str の型を持つため、型が一致しません