Rustの名前解決(1/5) 名前解決にかかわる構文

概要: Rustの名前解決の詳細について解説する。本記事では、名前解決に関する構文を紹介する。

  1. 名前解決にかかわる構文
  2. インポート解決
  3. パス解決
  4. メソッド記法とメンバ変数と関連アイテムの解決
  5. 可視性判定

Rustのモジュール

Rustのコンパイルはcrate単位で行われ、必ずcrateのトップレベルモジュールに相当するファイルが存在する。これには lib.rsmain.rs という名前がついていることが多い。

モジュール内にさらにモジュールを宣言する方法は2つある。 (parse_item_mod)

  • ひとつは mod foo; と書き、内容を別のファイルに書く方法でである。
  • もうひとつは mod foo { ... } のように内容を親モジュールと同じファイルに書く方法である。

mod foo 形式の場合は、以下の条件でファイル名が選択される。(submod_path)

  1. #[path="custom_path"] があれば、これが採用される。
  2. それがなければ、 foo.rs または foo/mod.rs のいずれかが採用される。両方ある場合はエラーになる。

上記のファイル名は、モジュールに対応するディレクトリからの相対パスとして扱われる。モジュールに対するディレクトリ割り当ては以下の規則に従う。

  • トップレベルのファイル ( lib.rsmain.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)

  • 型の名前空間に属するもの
    • mod
    • extern crate
    • struct (タプル形式の場合は、型と値の両方の名前空間に属する。)
    • enum
    • enum のバリアント名 (型と値の両方の名前空間に属する。)
    • union
    • type
    • trait
    • trait の関連型
  • 値の名前空間に属するもの
    • static
    • const
    • fn
    • extern { ... } の中身
    • タプル形式の struct (型と値の両方の名前空間に属する。)
    • enum のバリアント名 (型と値の両方の名前空間に属する。タプル形式でも波括弧形式でも適用される。)
    • trait の関連アイテムで、型以外のもの
  • マクロ名前空間に属するもの
    • マクロ定義
  • その他
    • use (インポートされたものの名前空間を引き継ぐ)

まとめ

Rustの名前解決について扱うために、まずは手始めとして文法を説明した。