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을 사용하여 함수를 호출했으므로, 반환 타입에 대해 [i32, u128]을 인자로 하여 EarlyBinder::instantiate를 호출합니다. 그 결과 인스턴스화된 반환 타입 i32를 얻게 되며, 이를 지역 변수 c의 타입으로 사용할 수 있습니다.
다음은 몇 가지 추가 예제입니다.
fn foo<T>() -> Vec<(u32, T)> { Vec::new() }
fn bar() {
// the return type of `foo` before instantiating it would be:
// `EarlyBinder(Adt(Vec, &[Tup(&[u32, T/#=0])]))`
// we then instantiate the binder with `[u64]` resulting in the type:
// `Adt(Vec, &[Tup(&[u32, u64])])`
let a = foo::<u64>();
}
struct Foo<A, B> {
x: Vec<A>,
..
}
fn bar(foo: Foo<u32, f32>) {
// the type of `foo`'s `x` field before instantiating it would be:
// `EarlyBinder(Vec<A/#0>)`
// we then instantiate the binder with `[u32, f32]` as those are the
// generic arguments to the `Foo` struct. This results in a type of:
// `Vec<u32>`
let y = foo.x;
}
컴파일러에서 이를 위한 instantiate 호출은 FieldDef::ty(소스)에서 이루어지며, 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 타입을 얻어야 합니다. 이는 impl의 DefId로 type_of 쿼리를 호출하여 얻을 수 있습니다. 그러나 impl 블록은 impl 바깥에 있을 경우 해제해야 할 수도 있는 제네릭 매개변수를 바인딩하므로, 이 쿼리는 EarlyBinder<Ty>를 반환합니다.
EarlyBinder 타입은 “이미 바인더 내부에 있을 때” 바인더를 해제하기 위한 instantiate_identity 함수를 제공합니다. 이는 사실상 EarlyBinder::instantiate(GenericArgs::identity_for_item(..))를 작성하는 것의 더 성능이 좋은 버전입니다. 개념적으로 이는 루트 유니버스의 플레이스홀더로 바인더를 인스턴스화하여 바인더를 해제합니다(이것이 무엇을 의미하는지는 다음 몇 장에서 다루겠습니다). 하지만 실제로는 아무런 수정 없이 내부 값을 그대로 반환할 뿐입니다.