ADT とジェネリック引数
ADT という用語は “Algebraic data type”(代数的データ型)を表し、Rust では struct、enum、または union を指します。
ADT の表現
MyStruct<u32> のような型の例を考えてみましょう。ここで MyStruct は次のように定義されています。
struct MyStruct<T> { x: u8, y: T }
型 MyStruct<u32> は TyKind::Adt のインスタンスになります。
Adt(&'tcx AdtDef, GenericArgs<'tcx>)
// ------------ ---------------
// (1) (2)
//
// (1) は `MyStruct` の部分を表します
// (2) は `<u32>`、つまり「置換」/ ジェネリック引数を表します
2 つの部分があります。
AdtDefは struct/enum/union を参照しますが、その型 パラメーターに対する値は含みません。 この例では、これは引数u32なし のMyStructの部分です。 (HIR では struct、enum、union は異なる形で表現されますが、ty::Tyでは、 それらはすべてTyKind::Adtを使って表現されることに注意してください。)GenericArgsは、ジェネリックパラメーターに置換される値のリストです。MyStruct<u32>の例では、[u32]のようなリストになります。 ジェネリクスと置換については、少し後で詳しく掘り下げます。
AdtDef と DefId
ソースコード内で定義されたすべての型には、一意の DefId があります(この
章を参照)。
これには ADT とジェネリクスが含まれます。
上で示した MyStruct<T> の定義では、
2 つの DefId があります。1 つは MyStruct 用で、もう 1 つは T 用です。
上のコードは u32 用の新しい DefId を生成しないことに注意してください。
なぜなら、それはそのコード内で定義されていない(参照されているだけ)からです。
AdtDef は、多くの便利なヘルパーメソッドを持つ DefId のラッパーのようなものです。
AdtDef と DefId の間には、基本的に 1 対 1 の関係があります。
tcx.adt_def(def_id) クエリを使って、DefId に対する AdtDef を取得できます。
AdtDef はすべて intern されており、これは 'tcx ライフタイムによって示されています。
質問: なぜ AdtDef の「内部」で置換しないのか?
ジェネリック struct を (AdtDef, args) で表現することを思い出してください。
では、なぜこの仕組みをわざわざ使うのでしょうか?
別の方法として、型を表現する際に、すべての型がすでに置換された、
完全に置換済みの AdtDef の形を常に新しく作成する、という選択もできたはずです。
これは手間が少ないように見えます。
しかし、(AdtDef, args) という仕組みには、これよりいくつか利点があります。
まず、(AdtDef, args) の仕組みには効率上の利点があります。
struct MyStruct<T> {
... 100s of fields ...
}
// やりたいこと: MyStruct<A> ==> MyStruct<B>
このような例では、A への 1 つの参照を B に置き換えるだけで、
MyStruct<A> を MyStruct<B>(など)として非常に低コストにインスタンス化できます。
しかし、すべてのフィールドを eager にインスタンス化した場合、
AdtDef 内のすべてのフィールドを走査して、
それらすべての型を更新しなければならない可能性があるため、はるかに多くの作業になることがあります。
もう少し深く言うと、これは Rust における struct が nominal 型であることに対応します。 つまり、それらはその名前によって定義されるということです(そして、その内容はその名前の定義からインデックス付けされ、 型そのものの「内部」に一緒に保持されるわけではありません)。
GenericArgs 型
ジェネリック型 MyType<A, B, …> が与えられた場合、MyType のジェネリック引数のリストを保存する必要があります。
rustc では、これは GenericArgs を使って行われます。
GenericArgs は、ジェネリック項目に対するジェネリック引数のリストを表す GenericArg のスライスへの thin pointer です。
たとえば、2 つの型パラメーター K と V を持つ struct HashMap<K, V> が与えられた場合、型 HashMap<i32, u32> を表すために使われる GenericArgs は &'tcx [tcx.types.i32, tcx.types.u32] によって表されます。
GenericArg は概念的には 3 つのバリアントを持つ enum で、型引数、const 引数、ライフタイム引数にそれぞれ 1 つずつ対応します。
実際には、これは GenericArgKind によって表現され、GenericArg はそれを
GenericArgKind に変換するメソッドを持つ、より空間効率の良いバージョンです。
実際の GenericArg struct は、型、ライフタイム、または const を、下位 2 ビットに discriminant を格納した intern 済みポインターとして保存します。
GenericArgs の実装そのものに取り組んでいる場合を除き、通常は GenericArg を直接扱う必要はなく、代わりに
GenericArg::unpack() メソッドを介して取得できる安全な GenericArgKind 抽象を利用するべきです。
場合によっては GenericArg を構築しなければならないことがあります。これは Ty/Const/Region::into() または GenericArgKind::pack によって行えます。
// ジェネリック引数を unpack および pack する例。
fn deal_with_generic_arg<'tcx>(generic_arg: GenericArg<'tcx>) -> GenericArg<'tcx> {
// 生の `GenericArg` を安全に扱うために unpack する。
let new_generic_arg: GenericArgKind<'tcx> = match generic_arg.unpack() {
GenericArgKind::Type(ty) => { /* ... */ }
GenericArgKind::Lifetime(lt) => { /* ... */ }
GenericArgKind::Const(ct) => { /* ... */ }
};
// `GenericArgKind` を pack してジェネリック args リストに格納する。
new_generic_arg.pack()
}
すべてをまとめると、次のようになります。
struct MyStruct<T>(T);
type Foo = MyStruct<u32>
Foo 型エイリアス内に書かれた MyStruct<U> については、次のように表現します。
MyStructに対するAdtDef(および対応するDefId)があります。- リスト
[GenericArgKind::Type(Ty(u32))]を含むGenericArgsがあります。 - そして最後に、上に挙げた
AdtDefとGenericArgsを持つTyKind::Adtがあります。
ネストしたジェネリック args
struct MyStruct<T>(T);
impl<T> MyStruct<T> {
fn func<T2, T3>() {}
}
fn main() {
MyStruct::<u32>::func::<bool, char>();
}
構文 MyStruct::<u32>::func::<bool, char> はタプルによって表現されます。つまり、func を指す DefId と、
すべての包含するジェネリックパラメーターを「たどる」GenericArgs リストです。この場合、そのリストは [u32, bool, char] になります。
generics_of クエリによって返される ty::Generics 型は、ネストした階層がどのように
リストへ平坦化されるかに関する情報を含み、GenericArgs リスト内のどのインデックスがどの
ジェネリックに対応するかを把握できるようにします。
その動作の一般的な考え方は、外側から内側へ(例では T が T2 より前)、左から右へ
(T2 が T3 より前)というものですが、いくつかの複雑な点があります。
- trait には暗黙の
Selfジェネリックパラメーターがあり、これは最初(つまり 0 番目)のジェネリックパラメーターです。Selfはすべての状況でジェネリックパラメーターを意味するわけではないことに注意してください。Res::SelfTyAlias と Res::SelfCtor を参照してください。 - early-bound ジェネリックパラメーターのみが含まれ、late-bound ジェネリクスは含まれません。
- … などです …
平坦化がどのように動作するかの正確な詳細については、ty::Generics を確認してください。