Binder와 상위 랭크 영역
때로는 제네릭 매개변수를 아이템이 아니라 타입이나 where 절의 일부로 정의하기도 합니다. 예를 들어, 타입 for<'a> fn(&'a u32)와 where 절 for<'a> T: Trait<'a>는 모두 'a라는 이름의 제네릭 라이프타임을 도입합니다. 현재 for<T>나 for<const N: usize>에 대한 안정화된 구문은 없지만, nightly에서는 feature(non_lifetime_binders)를 사용하여 for<T>/for<const N: usize>를 사용하는 where 절을 작성할 수 있습니다(타입은 작성할 수 없습니다).
for는 새로운 이름을 스코프에 도입하기 때문에 “바인더(binder)“라고 부릅니다. rustc에서는 Binder 타입을 사용하여 이러한 매개변수가 어디에서 도입되는지와 매개변수가 무엇인지(즉, 몇 개인지, 그리고 각 매개변수가 타입/상수/영역 중 무엇인지)를 추적합니다. for<'a> fn(&'a u32)와 같은 타입은 rustc에서 다음과 같이 표현됩니다.
Binder(
fn(&RegionKind::Bound(DebruijnIndex(0), BoundVar(0)) u32) -> (),
&[BoundVariableKind::Region(...)],
)
이러한 매개변수의 사용은 RegionKind::Bound(또는 TyKind::Bound/ConstKind::Bound 배리언트)로 표현됩니다. 이러한 바운드 영역/타입/상수는 두 가지 주요 데이터로 구성됩니다.
- 어느 바인더를 가리키는지 지정하는 DebruijnIndex입니다.
Binder가 도입하는 매개변수 중 어느 것을 가리키는지 지정하는BoundVar입니다.
또한 진단 목적으로 BoundTyKind/BoundRegionKind를 통해 추가 정보를 저장하기도 하지만, 이는 타입 동등성이나 더 일반적으로는 Ty의 의미론에 중요하지 않습니다. (위 예시에서는 생략했습니다)
디버그 출력에서는(그리고 서로 비공식적으로 이야기할 때도) 이러한 바운드 변수를 ^DebruijnIndex_BoundVar 형식으로 쓰는 경향이 있습니다. 위 예시는 이 형식으로 쓰면 Binder(fn(&'^0_0), &[BoundVariableKind::Region])가 됩니다. DebruijnIndex가 0일 때는 이를 생략하고 ^0이라고 쓰기도 합니다.
또 다른 구체적인 예시로, 이번에는 where 절과 타입에 for<'a>가 섞여 있는 경우입니다.
where
for<'a> Foo<for<'b> fn(&'a &'b T)>: Trait,
이는 다음과 같이 표현됩니다.
Binder(
Foo<Binder(
fn(&'^1_0 &'^0 T/#0),
[BoundVariableKind::Region(...)]
)>: Trait,
[BoundVariableKind::Region(...)]
)
'^1_0이 'a 매개변수를 가리킨다는 점에 주목하십시오. 가장 안쪽 바인더에서 한 단계 위의 바인더를 가리키기 위해 1인 DebruijnIndex를 사용하고, 첫 번째로 바인딩된 매개변수인 'a를 가리키기 위해 0인 var를 사용합니다. 또한 'b 매개변수를 가리키기 위해 '^0을 사용하는데, DebruijnIndex가 0(가장 안쪽 바인더를 가리킴)이므로 이를 생략하여, 첫 번째로 바인딩된 매개변수인 'b를 가리키는 boundvar 0만 남깁니다.
예전에는 각 Binder가 도입하는 바운드 변수의 집합을 항상 명시적으로 추적하지는 않았으며, 이로 인해 여러 버그가 발생했습니다(구체적으로는 ICE #81193, #79949, #83017). 이를 명시적으로 추적함으로써, 상위 랭크 where 절/타입을 구성할 때 탈출하는 바운드 변수나 다른 바인더의 변수가 없다는 것을 단언할 수 있습니다. 바인더 내부에 잘못된 타입이 있는 다음 예시를 보십시오.
Binder(
fn(&'^1_0 &'^1 T/#0),
&[BoundVariableKind::Region(...)],
)
'^1_0 영역은 가장 바깥쪽 바인더보다 더 높은 수준의 바인더를 가리키므로, 즉 탈출하는 바운드 변수이므로 온갖 문제를 일으킵니다. '^1 영역('^0_1로도 쓸 수 있음) 역시 그것이 가리키는 바인더가 두 번째 매개변수를 도입하지 않으므로 잘못된 형태입니다. 오늘날의 rustc는 이 두 가지 이유로 이 바인더를 구성할 때 ICE를 일으킵니다. 과거에는 이것이 그냥 동작하도록 허용했다가 코드베이스의 다른 부분에서 문제를 겪었을 것입니다.