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 は新しい名前をスコープに導入するため、「バインダー」と呼ばれます。
rustc では、これらのパラメータがどこで導入されるか、またそのパラメータが何であるか(つまり、数はいくつか、パラメータが型/const/リージョンのどれか)を追跡するために Binder 型を使用します。for<'a> fn(&'a u32) のような型は、rustc では次のように表現されます。
Binder(
fn(&RegionKind::Bound(DebruijnIndex(0), BoundVar(0)) u32) -> (),
&[BoundVariableKind::Region(...)],
)
これらのパラメータの使用は、RegionKind::Bound(または TyKind::Bound/ConstKind::Bound バリアント)によって表現されます。
これらの束縛リージョン/型/const は、主に 2 つのデータから構成されます。
- どのバインダーを参照しているかを指定するための DebruijnIndex。
Binderが導入するパラメータのうち、どれを参照しているかを指定するBoundVar。
また、診断上の理由から BoundTyKind/BoundRegionKind を介して追加情報を保存することもありますが、
これは型の等価性、またはより一般的には Ty のセマンティクスにとって重要ではありません。
(上記の例からは省略)
デバッグ出力(およびお互いに話すときの非公式な表現)では、
これらの束縛変数を ^DebruijnIndex_BoundVar の形式で書く傾向があります。
上記の例は、代わりに Binder(fn(&'^0_0), &[BoundVariableKind::Region]) と書かれます。
DebruijnIndex が 0 の場合は、それを省いて ^0 と書くこともあります。
もう 1 つの具体例として、今回は 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 に 1 を使用し、束縛された最初のパラメータである 'a を参照するために var に 0 を使用しています。
また、'^0 を使用して 'b パラメータを参照しています。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 とも書けます)も、それが参照するバインダーが 2 番目のパラメータを導入していないため、不正な形式です。
現在の rustc は、これら両方の理由により、このバインダーを構築するときに ICE します。
以前は、単にこれが動作することを許可してしまい、その後コードベースの他の部分で問題に遭遇していました。