ムーブパス
実際には、初期化をローカル変数の粒度で追跡するだけでは十分ではありません。Rust では、フィールド粒度でのムーブや初期化も可能です。
fn foo() {
let a: (Vec<u32>, Vec<u32>) = (vec![22], vec![44]);
// a.0 と a.1 はどちらも初期化されている
let b = a.0; // a.0 をムーブする
// a.0 は初期化されていないが、a.1 はまだ初期化されている
let c = a.0; // エラー
let d = a.1; // OK
}
これに対処するために、初期化を ムーブパス の粒度で追跡します。MovePath は、ユーザーが初期化したり、ムーブしたりできる何らかの場所を表します。たとえば、ローカル変数 a を表すムーブパスがあり、a.0 を表すムーブパスもあります。ムーブパスは、MIR の Place の概念におおよそ対応しますが、ムーブ解析をより効率的に行えるような方法でインデックス付けされています。
ムーブパスインデックス
MovePath データ構造は存在しますが、それらが直接参照されることはありません。代わりに、すべてのコードは MovePathIndex 型の インデックス を受け渡します。ムーブパスに関する情報を取得する必要がある場合は、このインデックスを MoveData の move_paths フィールド とともに使用します。たとえば、MovePathIndex mpi を MIR の Place に変換するには、次のように MovePath::place フィールドにアクセスできます。
move_data.move_paths[mpi].place
ムーブパスの構築
MIR 借用チェックで最初に行うことの 1 つは、ムーブパスの集合を構築することです。これは MoveData::gather_moves 関数の一部として行われます。この関数は MoveDataBuilder と呼ばれる MIR ビジターを使用して MIR を走査し、その中の各 Place がどのようにアクセスされているかを調べます。そのような各 Place について、対応する MovePathIndex を構築します。また、その特定のムーブパスがいつ/どこでムーブ/初期化されるかも記録しますが、これについては後のセクションで扱います。
不正なムーブパス
使用される すべての Place に対して実際にムーブパスを作成するわけではありません。特に、ある Place からムーブすることが不正である場合、MovePathIndex は不要です。いくつか例を挙げます。
- 配列の個々の要素をムーブすることはできないため、たとえば
foo: [String; 3]がある場合、foo[1]に対するムーブパスは存在しません。 - 借用された参照の内部からムーブすることはできないため、たとえば
foo: &Stringがある場合、*fooに対するムーブパスは存在しません。
これらのルールは move_path_for 関数によって強制されます。この関数は Place を MovePathIndex に変換します。先ほど説明したようなエラーケースでは、この関数は Err を返します。その結果、それらの場所が初期化されているかどうかを追跡する手間が不要になります(これによりオーバーヘッドが低減されます)。
プロジェクション
ムーブパス内のプロジェクションは、PlaceElem を使用する代わりに MoveSubPath として格納されます。
外へムーブできないプロジェクションと、スキップ可能なプロジェクションは表現されません。
配列のサブスライスプロジェクション(スライスパターンによって生成されるもの)は特別です。これらは、サブスライス内の各要素に対して 1 つずつ、複数の ConstantIndex サブパスに変換されます。
ムーブパスの検索
Place があり、それを MovePathIndex に変換したい場合は、MoveData の rev_lookup フィールドにある MovePathLookup 構造体を使用して行うことができます。2 つの異なるメソッドがあります。
find_localは、ローカル変数を表すmir::Localを受け取ります。これはより簡単なメソッドです。なぜなら、すべてのローカル変数に対して 常にMovePathIndexを作成するからです。findは、任意のPlaceを受け取ります。このメソッドは少し使いにくいです。というのも、(「不正なムーブパス」セクションで説明したように)すべてのPlaceに対してMovePathIndexがあるわけではないからです。そのため、findは、存在するもののうち見つけることができた最も近いパスを示すLookupResultを返します(たとえばfoo[1]に対しては、fooのパスだけを返すことがあります)。
相互参照
前述のように、ムーブパスは大きなベクターに格納され、MovePathIndex を介して参照されます。ただし、このベクター内では、それらはツリーとしても構造化されています。したがって、たとえば a.b.c に対する MovePathIndex がある場合、その親ムーブパスである a.b に移動できます。すべての子パスを反復処理することもできます。つまり、a.b から、パス a.b.c を見つけるために反復処理できます(ここで反復処理しているのは、ソース内で 実際に参照されている パスだけであり、参照され得た 可能な すべてのパスではありません)。これらの参照は、たとえば find_in_move_path_or_its_descendants 関数で使用されます。この関数は、あるムーブパス(例: a.b)またはそのムーブパスの任意の子(例: a.b.c)が、指定された述語に一致するかどうかを判定します。