Rustの名前解決(4/5) メソッド記法とメンバ変数と関連アイテムの解決
概要: Rustの名前解決の詳細について解説する。本記事では、型情報を必要とする名前の解決を説明する。
- 名前解決にかかわる構文
- インポート解決
- パス解決
- メソッド記法とメンバ変数と関連アイテムの解決
- 可視性判定
曖昧性が生じる例
この記事で扱うのは、型情報がないと曖昧性があるような名前の解決である。まずは例を挙げる。
メソッド記法は以下のような曖昧性がある。
trait Foo { fn f(&self) { println!("Foo"); } } trait Bar { fn f(&self) { println!("Bar"); } } struct A; impl Foo for A {} fn main() { let x = A; x.f(); }
この例では、 A::f() が Foo::f と Bar::f のいずれを指しているかを決定するために、 x の型を決定した上でトレイト実装を検索する必要がある。
メンバ変数は以下のような曖昧性がある。
struct A { m: u32, } struct B { m: u16, } fn main() { let x = A { m: 0 }; println!("{}", std::mem::size_of_val(&x.m)); }
この例では、 x.m が A と B のどちらのメンバ変数 m であるかを決定するために、 x の型を決定する必要がある。
関連型(関連アイテムの一種)は以下のような曖昧性がある。
trait Foo { type X; } trait Bar { type X; } fn f<T:Foo>() { println!("{}", std::mem::size_of::<T::X>()); } fn g<T:Bar>() { println!("{}", std::mem::size_of::<T::X>()); } struct A; impl Foo for A { type X = u16; } impl Bar for A { type X = u32; } fn main() { f::<A>(); }
この例では、 T::X が <T as Foo>::X なのか <T as Bar>::X なのかを決定するために、 T のtrait boundを参照する必要がある。
メソッド(関連アイテムの一種)は以下のような曖昧性がある。
trait Foo { fn f() { println!("Foo"); } } trait Bar { fn f() { println!("Bar"); } } struct A; impl Foo for A {} fn main() { A::f(); }
この例では、 A::f() が Foo::f と Bar::f のいずれを指しているかを決定するために、トレイト実装を検索する必要がある。
トレイトの列挙
以下に挙げる名前解決のうちの一部では、「スコープ内にある利用可能なトレイト」の一覧を出す必要のあるものがある。この処理は、assemble_extension_candidates_for_traits_in_scopeで行われている。この一覧は、 def_map と同様の trait_map という変数に計算済みのものがあり、これを取り出している。
trait_map の計算は rustc_resolve::Resolver::get_traits_containing_item で行われている。これによると、検索範囲は
- 自身のいるモジュール
- 自身のいるモジュールの祖先
- prelude (明示的に除外しない限り)
であることがわかる。
この中で、 所望の識別子を含んでいるトレイトのみが、 trait_map に追加されている。
所望の識別子を含んでいても、もとの型がそのトレイトを実装していなければ採択されない。この処理は consider_candidates 内の consider_probe で行われている。
pick_methodによると、上記の条件を満たしたトレイトが複数あると基本的にエラーになる。ただし、inherent implとtrait implで衝突した場合は、inherent implが優先される。
メソッド記法の解決
メソッド記法の解決はやや複雑であり、「名前解決」から離れる面もあるためこの記事では深入りしない。メソッド記法を処理する部分のREADME にそれなりに説明がある。
要点は、自動デリファレンスと自動リファレンスやunsizingなどの自動変換によりいくつかの候補となる型が生成され、それらに優先順位がつけられる。それぞれの型について、上に挙げたようなトレイトの検索が行われる。
メンバ変数の解決
rustc_typeck::check の check_field で解決される。これが呼ばれた時点で、もとの構造体の型が判明していると仮定している。
関連型の解決
関連型の解決はrustc_typeck::astconv::AstConv::ast_ty_to_tyで行われる。ここではまず、 <QSelf>::AssocTyp における <QSelf> が、解決済みのパスであることをチェックしている。解決済みのパスならば、紐づけられた Def を取得する。
その後、 rustc_typeck::astconv::AstConv::associated_path_def_to_ty に処理がうつる。ここでは、得られた <QSelf> のDefによりさらに条件分岐している。コメントにある通り、ここで出てくる <QSelf> は実は Self か型パラメーターでなければならない。
Self や型パラメーターの場合は、このパラメーターが導入された箇所で、 where 等によるtrait boundが与えられているはずなので、そこからtraitを検索する。所望の関連型を所有しているトレイトがちょうど1つあれば、それを答えとする。
メソッドの解決
型以外の関連ジャイテムの解決は rustc_typeck::check::resolve_ty_and_def_ufcs で行われる。この処理はさらに rustc_typeck::check::method::probeのprobe_for_name に移譲される。あとは上で説明したように、該当する実装が1つあったときだけ採用される。
まとめ
Rustの識別子の中には、型に依存して解決されるものがある。これらは比較的アドホックな方法で解決されており、型の明示が必要な原因のひとつになっていると思われる。