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

EarlyBinder とパラメーターのインスタンス化

ジェネリックパラメーター T を導入するアイテムがある場合、foo の外側から foo の内側の型(つまり戻り値の型や引数の型)を参照するときは常に、foo 上で定義されたジェネリックパラメーターを扱うよう注意しなければなりません。例を示します。

fn foo<T, U>(a: T, _b: U) -> T { a }

fn main() {
    let c = foo::<i32, u128>(1, 2);
}

main を型検査するとき、単純に foo の戻り値の型を見て変数 c に型 T を割り当てることはできません。関数 main はジェネリックパラメーターを一切定義しておらず、このコンテキストでは T はまったく意味を持ちません。より一般的に言えば、あるアイテムがジェネリックパラメーターを導入(束縛)する場合、そのアイテムの外側からアイテム内の型にアクセスするときには、ジェネリックパラメーターを外側のアイテムからの値でインスタンス化しなければなりません。

rustc では、これを EarlyBinder 型で追跡します。foo の戻り値の型は EarlyBinder<Ty> として表現され、Ty にアクセスする唯一の方法は、Ty が使用している可能性のあるジェネリックパラメーターに引数を提供することです。これは EarlyBinder::instantiate メソッドによって実装されており、このメソッドはバインダーを解消し、すべてのジェネリックパラメーターを提供された引数で置き換えた内側の値を返します。

先ほどの例に戻ると、main を型検査するとき、foo の戻り値の型は EarlyBinder(T/#0) として表現されます。その後、この関数をジェネリック引数 i32, u128 で呼び出しているため、戻り値の型に対して EarlyBinder::instantiate を呼び出し、args として [i32, u128] を渡します。その結果、インスタンス化された戻り値の型は i32 となり、これをローカル変数 c の型として使用できます。

さらにいくつか例を示します。

fn foo<T>() -> Vec<(u32, T)> { Vec::new() }
fn bar() {
    // インスタンス化する前の `foo` の戻り値の型は次のようになります。
    // `EarlyBinder(Adt(Vec, &[Tup(&[u32, T/#=0])]))`
    // 次に、バインダーを `[u64]` でインスタンス化し、結果として次の型になります。
    // `Adt(Vec, &[Tup(&[u32, u64])])`
    let a = foo::<u64>();
}
struct Foo<A, B> {
    x: Vec<A>,
    ..
}

fn bar(foo: Foo<u32, f32>) { 
    // インスタンス化する前の `foo` の `x` フィールドの型は次のようになります。
    // `EarlyBinder(Vec<A/#0>)`
    // 次に、`Foo` 構造体へのジェネリック引数がそれらであるため、
    // バインダーを `[u32, f32]` でインスタンス化します。その結果、次の型になります。
    // `Vec<u32>`
    let y = foo.x;
}

コンパイラーでは、このための instantiate 呼び出しは FieldDef::tysrc)で行われます。bar の型検査中のどこかで、foo.x の型を取得するために最終的に FieldDef::ty(x, &[u32, f32]) を呼び出すことになります。

インデックスに関する注記: Param のインデックスが EarlyBinder が束縛するものと一致しない場合、それはバグです。たとえば、インデックスが範囲外である場合や、ライフタイムのインデックスが型パラメーターに対応している場合です。この種のエラーは、コンパイラーのより早い段階の名前解決中に検出されます。そこでは、内側のアイテムから名前で参照可能であるべきでないアイテムによって導入されたジェネリックパラメーターへの参照を許可しません。


前述のように、アイテムの_外側_にいるときは、内側の値にアクセスする前に EarlyBinder をジェネリック引数でインスタンス化することが重要です。しかし、概念的にすでにバインダーの内側にいる場合の設定は少し異なります。

例を示します。

#![allow(unused)]
fn main() {
impl<T> Trait for Vec<T> {
    fn foo(&self, b: Self) {}
}
}

b パラメーターの型を表す Ty を構築するとき、現在内側にいる impl 上の Self の型を取得する必要があります。これは implDefIdtype_of クエリを呼び出すことで取得できます。ただし、impl ブロックはジェネリックパラメーターを束縛しており、impl の外側にいる場合にはそれらを解消しなければならない可能性があるため、この呼び出しは EarlyBinder<Ty> を返します。

EarlyBinder 型は、「すでにその内側にいる」場合にバインダーを解消するための instantiate_identity 関数を提供します。これは実質的に、EarlyBinder::instantiate(GenericArgs::identity_for_item(..)) と書くことの、より高性能なバージョンです。概念的には、これはルート universe 内のプレースホルダーでインスタンス化することによってバインダーを解消します(これが何を意味するかについては、次の数章で説明します)。ただし実際には、変更を一切行わずに内側の値を返すだけです。