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 T は T に対して不変です。
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 つの例を示します:
x1とx2の 2 つの変数があり、x1はライフタイム'static、x2はライフタイム'aを持つとします。明らかにx1のほうがx2より有用です。なぜならより長く生存するからです。take_func1とtake_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";では.sはsフィールドへのアクセスを意味します(この時点でフィールドの値は&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の型を持つため、型が一致しません
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.