Rustの名前解決(1/5) 名前解決にかかわる構文
概要: Rustの名前解決の詳細について解説する。本記事では、名前解決に関する構文を紹介する。
- 名前解決にかかわる構文
- インポート解決
- パス解決
- メソッド記法とメンバ変数と関連アイテムの解決
- 可視性判定
Rustのモジュール
Rustのコンパイルはcrate単位で行われ、必ずcrateのトップレベルモジュールに相当するファイルが存在する。これには lib.rs や main.rs という名前がついていることが多い。
モジュール内にさらにモジュールを宣言する方法は2つある。 (parse_item_mod)
- ひとつは
mod foo;と書き、内容を別のファイルに書く方法でである。 - もうひとつは
mod foo { ... }のように内容を親モジュールと同じファイルに書く方法である。
mod foo 形式の場合は、以下の条件でファイル名が選択される。(submod_path)
#[path="custom_path"]があれば、これが採用される。- それがなければ、
foo.rsまたはfoo/mod.rsのいずれかが採用される。両方ある場合はエラーになる。
上記のファイル名は、モジュールに対応するディレクトリからの相対パスとして扱われる。モジュールに対するディレクトリ割り当ては以下の規則に従う。
- トップレベルのファイル (
lib.rsやmain.rsという名前のことが多い) には、直近の親ディレクトリが割り当てられる。 - ファイル名が
mod.rsのモジュールファイルには、直近の親ディレクトリが割り当てられる。 - それ以外の名前のモジュールファイルには、ディレクトリは割り当てられない。
mod foo { ... }形式で定義した場合は、親モジュール直下のfooディレクトリが仮想的に割り当てられる。
外部モジュールの取り込み処理は構文解析時に行われる。したがって1つのcrateのコンパイルの途中では、1つの大きなASTが生成される。
アイテム
モジュールはアイテムを含むことができる。これには mod, use, extern crate, extern{ fn ... }, fn, static, const, type, enum, struct, union, trait, impl などがある。 (parse_item_)
mod, use, extern crate は名前解決で特殊扱いされるが、それ以外はほぼ同様に扱われる。ただし、 enum のコンストラクタは SomeEnum::Constr のように参照するため、 enum 自体がモジュールのように振る舞う。
モジュールではないが子要素を持つアイテムもある。例えば、型やトレイトにはメソッドが関連づけられている。
パス
識別子を :: で繋いだものをパスという。ただし例えば以下のような変種がある。 (parse_path, parse_qualified_path, parse_view_path)
::で始めることで、絶対パスであることを明示できる。self::で始めると、相対パスであることを明示できる。superという特殊なパス要素を使うと、親モジュールを参照できる。- 各パス要素に、
<>により型引数や生存期間引数を与えることができる場合がある。この記法は型であることが明白な文脈ではFoo<Bar>のように書き、式と紛らわしい文脈ではFoo::<Bar>のように書く。 <>のかわりに()で囲まれた型のリストや、-> Typeを与えることができる場合がある。- パスの最初の要素として
<A as Foo>のような形の指定をとることができる場合がある。 - パスの最後に
foo::{bar1, bar2}やfoo::*のような複数指定が可能な場合がある。
extern crate
それぞれのcrateが、Rustのモジュールツリーを1つ有している。 extern crate をすると、特定のcrateを現在コンパイル中のcrateのツリーから参照できるようになる。*NIXのファイルシステムに慣れた人なら、これはデバイスを特定のディレクトリにマウントするようなものだと考えるとわかりやすいだろう。
通常 extern crate crate_name; の形で使うが、 extern crate some_crate as mount_point; のように別名を与えることもできる。 (parse_item_extern_crate)
use
use は異なるモジュールに属するアイテムに対する参照を張る。*NIXのファイルシステムに慣れた人なら、これはシンボリックリンクと考えるとわかりやすいだろう。
use の基本形は以下の2つである。
// simple import use foo::bar as baz; // glob import use foo::bar::*;
このうち、simple importに対しては以下のような構文糖衣がある。 (parse_view_path)
use foo::bar; // use foo::bar as bar; use foo::{bar as baz, bar2}; // use foo::bar as baz; use foo::bar2 as bar2;
pub による可視範囲指定
以下の位置には、 pub による可視範囲を指定できる。 (parse_visibility)
fn,struct,enumなど、ほぼ全てのアイテム。 (parse_item_内)impl { ... }の中にある実装アイテム。 (parse_impl_item内)extern { ... }の中にある外部アイテム。 (parse_foreign_item内)- 構造体および列挙体のフィールド型(タプル形式の場合)またはフィールド名(波括弧形式の場合)。ただし列挙体のそれについては冗長であり不要。 (
parse_tuple_struct_body内およびparse_struct_decl_field内)struct A(pub u32);struct A { pub x: u32 }enum A { A0(pub u32) } // 冗長enum A { A0 { pub x: u32 } } // 冗長
Rust 1.16.0 では可視範囲は pub と無印の2択だが、Rust RFC 1422: pub(restricted)による拡張がnightlyには実装されている。1.16.0に実装されているものと異なる構文だが、現在のnightlyでは以下のような構文になっている。 (現時点での最新版の parse_visibility)
pub… あらゆる場所から可視。pub(crate)… 現在のcrateから可視。pub(in path::to::somewhere)… 特定モジュールの子孫からのみ可視。pub(self),pub(super)…pub(in self)/pub(in super)の略記。- 指定なし …
pub(in self)と同義。
名前空間
Rustでいうところの「名前空間」は、C++の名前空間ではなくCの名前空間(default namespace, struct namespace, labels, member names)のようなものを指す。
Rustには3つの名前空間がある: 型の名前空間、値の名前空間、マクロの名前空間である。 (rustc_resolve::Namespace)
同じ識別子でも、名前空間が異なれば、別のものとして扱われる。主要な識別子の名前空間は以下の通りである。 (rustc_resolve::build_reduced_graph::Resolver::build_reduced_graph_for_item)
まとめ
Rustの名前解決について扱うために、まずは手始めとして文法を説明した。