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の識別子の中には、型に依存して解決されるものがある。これらは比較的アドホックな方法で解決されており、型の明示が必要な原因のひとつになっていると思われる。