Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ユニバーサルリージョン

「ユニバーサルリージョン」は、コードが「名前付きライフタイム」– たとえばライフタイムパラメーターや 'static – を指すために使用する名前です。この名前は、そのようなライフタイムが「全称量化」されているという事実に由来します(つまり、それらのライフタイムのすべての値に対してコードが正しいことを確認しなければなりません)。リージョン推論中にライフタイムパラメーターがどのように扱われるかについて、少し議論しておく価値があります。次の例を考えてみましょう。

fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'b u32 {
  x
}

この例はコンパイルされないことを意図しています。なぜなら、型が &'a u32 である x を返している一方で、シグネチャでは &'b u32 の値を返すことを約束しているからです。しかし、'a'b のようなライフタイムはリージョン推論にどのように統合され、このエラーはどのように検出されることになるのでしょうか。

ユニバーサルリージョンとそれら相互の関係

リージョン推論の初期段階で、最初に行うことの 1 つは UniversalRegions 構造体を構築することです。この構造体は、特定の関数のスコープ内にあるさまざまなユニバーサルリージョンを追跡します。また、それら相互の関係を追跡する UniversalRegionRelations 構造体も作成します。したがって、たとえば where 'a: 'b がある場合、UniversalRegionRelations 構造体は 'a: 'b が成り立つことが分かっている、という情報を追跡します(これは outlives 関数でテストできます)。

すべてはリージョン変数である

NLL リージョン推論の仕組みにおける重要な側面の 1 つは、すべてのライフタイムが番号付きの変数として表現されることです。これは、使用する region_kind::RegionKind のバリアントが ReVar バリアントだけであることを意味します。これらのリージョン変数は、そのインデックスに基づいて 2 つの主要なカテゴリに分けられます。

  • 0..N: ユニバーサルリージョン – ここで議論しているものです。この場合、コードは宣言された関係を満たすこれらの変数の任意の値に関して正しくなければなりません。
  • N..M: 存在リージョン – リージョン推論器が何らかの適切な値を見つけることを課される推論変数です。

実際には、ユニバーサルリージョンは、それらがどこでスコープに導入されたかに基づいてさらに細分化できます(RegionClassification 型を参照してください)。これらの細分化は、ここで議論するトピックにとっては重要ではありませんが、クロージャ制約の伝播を考えるときに重要になるため、そこで議論します。

リージョンの値の要素としてのユニバーサルライフタイム

前述のとおり、各リージョンについて推論する値は集合 {E} です。この集合の要素は制御フローグラフ内の地点である場合もありますが、各ユニバーサルライフタイム 'a に対応する要素 end('a) である場合もあります。あるリージョン R0 の値に end('a) が含まれている場合、これは R0 が呼び出し元における 'a の終端まで延長されなければならないことを意味します。

ユニバーサルリージョンの「値」

リージョン推論中、他のリージョンの値を計算するのと同じ方法で、各ユニバーサルリージョンの値を計算します。この値は、実質的に、そのユニバーサルリージョンの下限 – それが outlive しなければならないもの – を表します。次に、この値を使用してエラーをチェックする方法を説明します。

生存性とユニバーサルリージョン

すべてのユニバーサルリージョンには、関数本体全体を含む初期生存性制約があります。これは、ライフタイムパラメーターが呼び出し元で定義され、この特定の関数を呼び出す関数呼び出し全体を含まなければならないためです。さらに、各ユニバーサルリージョン 'a は、自身(つまり end('a))をその生存性制約に含みます(すなわち、'a は自身の終端まで延長されなければなりません)。コード内では、これらの生存性制約は init_free_and_bound_regions で設定されます。

ユニバーサルリージョンの outlives 制約の伝播

では、このセクションの最初の例を考えてみましょう。

fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'b u32 {
  x
}

ここで x を返すには、&'a u32 <: &'b u32 が必要であり、これは outlives 制約 'a: 'b を生じさせます。これをデフォルトの生存性制約と組み合わせると、次のようになります。

'a live at {B, end('a)} // B は「関数本体」を表す
'b live at {B, end('b)}
'a: 'b

したがって、'a: 'b 制約を処理するとき、'a の値に end('b) を追加し、その結果、最終的な値は {B, end('a), end('b)} になります。

エラーの検出

制約の伝播が完了したら、あるユニバーサルリージョン 'a に要素 end('b) が含まれている場合、'a: 'b が関数の境界で宣言されていなければならない、という制約を強制します。宣言されていない場合、今回の例のように、それはエラーです。このチェックは check_universal_regions 関数で行われます。この関数は、すべてのユニバーサルリージョンを単純に反復処理し、それらの最終値を検査して、宣言された UniversalRegionRelations と照合します。