それを表現するための仕組みがあるからです。自体は識別子と名前空間からNameResolutionを引くマップですが、そのNameResolutionは以下のような定義になっています。最終的な解決結果ではなく、「暫定2位までの解決結果」と「この名前を定義しうる単独別名インポートの一覧」を保持しています。存在していても、これがglobインポート由来かつ他に単独インポートが存在するかもしれない場合はこの結果は確定ではないということになります。ここでは以下のような処理をしているわけです。上に挙げたデータ構造とは別に、解決済みインポートの一覧と未解決インポートのキューをグローバルに保持しておく。はじめは全て未解決扱いである。一回のループで、未解決インポートの解決を順番に試みる。未解決インポートの個数が減ったらループを継続し、減らなかったら収束とみなして停止する。インポートが未解決というのはどういう状態でしょうか。第一に、インポート元のモジュールがそもそも確定していない場合があります。由来かもしれず、その場合は手のつけようがありません。globインポートの場合、この時点でglobインポート自体は必ず「解決」扱いになります。いうのも、globインポート自体の「解決」は基本的には以下の2つの処理を(この順番で)するだけだからです。当該インポート指令を追加する。(→今後同期されることを保証)インポート元モジュールでこの時点で解決済みになっているDefを列挙し、インポート先に継ぎ木する。よるフックに移譲されます。単独別名インポートの場合は、3つの名前空間それぞれでインポートの可否を調べ、3つの結果がすべて確定した時点で解決済みになります。「この名前空間ではインポートできるかもしれないし、できないかもしれない」というものが残っているうちは未解決です。マクロ展開に由来する未解決状態も考えられますが、これはまた話がややこしくなるので別途説明します。して定義されています。含めてインポート解決によって解決されるものは以下のものがあることがわかります。baz;)この場合は、型名前空間のみがインポートされる。この形のインポートは実際にはインポート解決されず、かわりにパス解決で処理するためにモジュールをメモしておく。よるインポートインポート解決では使われない。よるインポートのマクロ部分インポート解決では使われない。rustfix用?モジュール解決のファイナライズ最小不動点アルゴリズムは、解決可能なものを処理した時点で停止します。それを確認しています。自体の仕様が、グロブのシャドウイングや名前空間の違いに由来する複雑さを抱えています。解決を順序不定で行う必要があり、そのためにRustコンパイラは、「どこまでが既に判明しているのか」という状態を慎重に更新しながら適切に状態を更新し続けるという難しい処理を実装しています。次回は可視性について扱います。可視性はインポート解決の動作にかかわっているものの、いったん可視性抜きで説明できることが多かったので分割しました。Rust2018以降、Rustのモジュールシステムは入門しやすく、かつマスターしにくくなりました。そこで本記事では、その両方を達成することを目指しました。同じテーマで、Rust2018を考慮して改めてまとめ直したものです。本記事中のサンプルコードの多くは、Rustの細かい仕様や実装を調べるためのものであり、実際に有用なプログラミングパターンであるとは限りません。実質的にバグに近い挙動を説明しているものもあるため、将来にわたって同じ動作であることはあまり保証できません。モジュール木からモジュールグラフへRustのモジュールはファイルシステムのように木構造がベースになっています。絡むと話が難しくなります。これはシンボリックリンクのようなもので、相互参照や再帰的なモジュールを作ることができてしまいます。importなどがあるため単純にはいきません。木構造といっても単純な木構造とは言えない事情がいくつかあります。これらの詳細な事情をひとつずつ解説していきます。名前空間Rustではひとつのモジュールに同じ名前のDefを最大3個まで紐付けられます。(namespace)といい、以下の3つの名前空間があります。(マクロ・属性)(C++やC#では、Rustのモジュールに対応するもののことを名前空間と呼んでいます。Rustの名前空間はC言語の名前空間などに近い概念だといえます。)たとえば、以下のようにタプル構造体を定義した場合は実際には2つのDefが紐付いています。(型名前空間)}レコード構造体の場合は型名前空間しか消費しないので、関数とのオーバーロードが可能です。}}※RFC0234の規定により、列挙型のレコードバリアントは値名前空間も予約することになっています。はトレイトとしてもderiveマクロとしても振る舞います。_Rust1.33.0から、以下のような別名インポートが可能になりました。Read/Writeをスコープ内トレイトの解決に利用する。このモジュールのRead/Writeという名前を汚染しない。作られた名前は重複しないという特殊仕様があります。idに割り当てることで実装されています。重複して別々の整数を割り当てるというハックが可能というわけです。importに含まれるようです。上記と同様の仕組みをとっています。53;これらはトレイトではないので、外から見て意味のある挙動はないはずです。除いて考えると)木構造になっています。あるという構造になります。依存している他のクレートはそれぞれが別のモジュール木になっています。モジュール木というのは不正確でモジュール森と考えたほうが正確です。他のクレートを参照できる仕組みについては後述します。以外に、モジュールとして振る舞うものが3つあります。enumとtraitとブロックです。}}enumがモジュールとして扱われているのはもちろん、以下を可能にするためです。self::MyEnum::*;traitがモジュールなのは名前解決時にメソッド名まで解決してしまいたいからです。(Defaultトレイトのdefaultかどうかははっきりしないので)(ただし、トレイトアイテムのuseは別途禁止されています)ブロックひとつひとつがモジュールであるおかげで、ローカルでしか使わない構造体を定義したり、一時的なインポートをすることができます。}}非正規モジュールは相対パスのときに無視されます。して定義されているものはDefとして親モジュールから名前で参照できますが、ブロックに対応する匿名モジュールは親モジュールからのリンクはなく、子から親へのリンクしかありません。型エイリアス列挙型のバリアント以下のコードは現在のRustでは動きません。可能にするために名前解決によって解決されます。ところが、名前解決の観点からは型エイリアスは同じDefではないので、列挙型に(モジュール的に)ぶら下がっているバリアントは引き継ぎません。この不便が解消される予定です。RFC2338の現状の実装では名前解決に手を入れていません。かわりに、型解決の段階で関連アイテムの亜種として処理しています。本記事の手前のほうで、「名前解決はパスを途中まで解決する」と書きましたが、それで言うと以下のようになります。しようとしている)がコンパイルできません。これが意図したものであるかどうかはやや不明で、eddyb氏の発言を見るにはじめからあまり考慮していないようにも見えます。必要な場面ではもとの列挙型から引っ張ってくれば十分とも言えるので、RFCの本文がoutdatedであると考える余地があります。使えるようにするとなった場合は、モジュール木の構造自体に何らかの影響を及ぼすと思われます。)を引くことについて言及していないので、今のところ名前解決への影響はなさそうです。まとめモジュール木はナイーブにはモジュールをノードとし、識別子をラベルとするラベルつき木といえますが、匿名モジュールや名前空間などがあるため正確にはより複雑な構造を持っていることを説明しました。挙動と、それを解決するためのアルゴリズムについて説明します。Rust2018以降、Rustのモジュールシステムは入門しやすく、かつマスターしにくくなりました。そこで本記事では、その両方を達成することを目指しました。同じテーマで、Rust2018を考慮して改めてまとめ直したものです。本記事中のサンプルコードの多くは、Rustの細かい仕様や実装を調べるためのものであり、実際に有用なプログラミングパターンであるとは限りません。実質的にバグに近い挙動を説明しているものもあるため、将来にわたって同じ動作であることはあまり保証できません。Rust2015までのモジュールまわりここまではRust2018イディオムを前提に説明をしてきましたが、以降は詳細な説明に入るためにRust2015(特に1.29以前)での状況も軽く説明します。始まる旧式の絶対パスを使っていました。いう特殊な絶対パスがありました。外部クレートのルートをローカルクレートのルート直下にマウントしていました。マクロは名前解決に統合されていませんでした。