モジュール
Module: ModuleDeclaration DeclDefs DeclDefs DeclDefs: DeclDef DeclDef DeclDefs DeclDef: AttributeSpecifier ImportDeclaration EnumDeclaration ClassDeclaration InterfaceDeclaration AggregateDeclaration Declaration Constructor Destructor UnitTest StaticConstructor StaticDestructor ConditionalDeclaration StaticAssert TemplateDeclaration TemplateMixin MixinDeclaration ;
モジュールはソースファイルと1対1に対応します。 モジュール名は、 ソースファイル名からパスと拡張子を除いたものになります。
モジュールは自動で、その中身をスコープとする名前空間となります。 モジュールは表面的にはクラスと似たところがありますが、別のものです:
- モジュール毎にインスタンスは1つだけで、 静的に割り当てられます。
- 仮想関数テーブルを持ちません。
- モジュールには継承や親モジュールといった概念がありません。
- 1つのファイルには1つだけモジュールが対応します。
- モジュール内のシンボルはimportできます。
- モジュールは常にグローバルスコープでコンパイルされ、 周りで宣言された属性に影響を受けることなどはありません。
モジュールは、package と呼ばれる階層にまとめることができます。
Modules offer several guarantees:
- モジュールを import する順序がセマンティクスに影響することはありません。
- どこから import されるかによって、 そのモジュールのセマンティクスが変わることもありません。
- モジュールが C がモジュール A と B をimportしてるときに、Bに対する変更によって、 Aに依存するC内のコードが勝手に変更されることはありません。
モジュール宣言
ModuleDeclaration では、モジュールの名前と、 属するパッケージを指定します。もし宣言が存在しなければ、 ソースファイル名と同じ名前(パスと拡張子は除かれる)が使用されます。
ModuleDeclaration: module ModuleFullyQualifiedName ; ModuleFullyQualifiedName: ModuleName Packages . ModuleName ModuleName: Identifier Packages: PackageName Packages . PackageName PackageName: Identifier
右端の一個前にある Identifier は、 モジュールが属するパッケージを指します。 パッケージはソースファイルの置かれているディレクトリに対応します。 予約語をパッケージ名にすることはできません。 従って、予約語を対応するディレクトリ名に使うこともできません。
もし ModuleDeclaration を書くならば、 ソースファイルの先頭に、一個だけ書く必要があります。
例:
module c.stdio; // これはモジュール stdio で、パッケージ c に属します
慣習として、パッケージ名とモジュール名は全て小文字で書きます。 この名前はOSのファイル名やディレクトリ名に対応していて、 多くのファイルシステムでは大文字小文字を区別しないからです。 全て小文字にすると決めておくことで、 プロジェクトを別のファイルシステムへ持っていったときの問題が少なくなります。
import宣言
テキストをそのまま#includeするのではなく、Dでは、import宣言によって シンボルだけをロードします:
ImportDeclaration: import ImportList ; static import ImportList ; ImportList: Import ImportBindings Import , ImportList Import: ModuleFullyQualifiedName ModuleAliasIdentifier = ModuleFullyQualifiedName ImportBindings: Import : ImportBindList ImportBindList: ImportBind ImportBind , ImportBindList ImportBind: Identifier Identifier = Identifier ModuleAliasIdentifier: Identifier
import宣言には、一般化されたものからより粒度を細かくしたものまで、 いくつか種類があります。
import 宣言の順番は特に意味を持ちません。
import 宣言の ModuleFullyQualifiedName は、 どのパッケージに含まれているかにかかわらず、 完全修飾が必要です。 import元モジュールからの相対パスの考慮などはなされません。
基本import
一番シンプルなimport文は、 単にimportしたいモジュールを列挙します:
import std.stdio; // モジュール stdio を std パッケージからimport import foo, bar; // モジュール foo と bar をimport void main() { writefln("hello!\n"); // std.stdio.writefln を呼び出す }
名前がカレントの名前空間から見つからなかった場合に、 import したモジュールから名前が探索されます。 import中で見つかった名前が唯一だった場合は、それが採用されます。 複数見つかった場合はエラーになります。
module A; void foo(); void bar();
module B; void foo(); void bar();
module C; import A; void foo(); void test() { foo(); // C.foo() の呼び出し。importよりもカレント名前空間を先に探索する bar(); // A.bar() の呼び出し。importが使われる }
module D; import A; import B; void test() { foo(); // エラー。A.foo() か B.foo() か曖昧 A.foo(); // ok。A.foo() B.foo(); // ok。B.foo() }
module E; import A; import B; alias B.foo foo; void test() { foo(); // B.foo() の呼び出し A.foo(); // A.foo() の呼び出し B.foo(); // B.foo() の呼び出し }
public import
デフォルトでは、import は private です。これは例えば モジュール A がモジュール B を、モジュール B はモジュール C をimportしていた場合、Cの名前はAでは探索されないということです。ただし、 import を特別に public と宣言すると、その import が取り込むモジュール内の全ての名前が、 現在のモジュールを外からimportするモジュールにも見えるようになります。
module A; void foo() { }
module B; void bar() { }
module C; import A; public import B; ... foo(); // A.foo() の呼び出し bar(); // B.bar() の呼び出し
module D; import C; ... foo(); // エラー。foo() は未定義 bar(); // ok。B.bar() の呼び出し
static import
基本importは、比較的少なめのモジュールとimportで成り立っているプログラムでは うまく働きます。しかしimportの数が増えてくると、 多数のモジュールをimportしたことによる名前の衝突が起こり始めます。 これを避ける手段のひとつが、static importです。 static import した名前は、 使うときには完全修飾しなければいけません:
static import std.stdio; void main() { writefln("hello!"); // エラー。writefln は未定義 std.stdio.writefln("hello!"); // ok。writefln は完全修飾されている }
改名 import
importするモジュールにローカルな名前を与えて、 その名前で修飾したアクセスを 強制することができます:
import io = std.stdio; void main() { io.writefln("hello!"); // ok。std.stdio.writefln の呼び出し std.stdio.writefln("hello!"); // エラー。std は未定義 writefln("hello!"); // エラー。writefln は未定義 }
改名importは、 とても長いモジュール名を扱うのに便利です。
選択 import
モジュールから一部のシンボルだけをimportして、 現在の名前空間で束縛することができます:
import std.stdio : writefln, foo = writef; void main() { std.stdio.writefln("hello!"); // エラー。std は未定義 writefln("hello!"); // ok。writefln はカレントの名前空間で束縛されている writef("world"); // エラー。writef は未定義 foo("world"); // ok。std.stdio.writef() の呼び出し fwritefln(stdout, "abc"); // エラー。fwritefln は未定義 }
static は選択importに対しては指定できません。
改名選択import
改名importと選択importを組み合わせた例です:
import io = std.stdio : foo = writefln; void main() { writefln("bar"); // エラー。writefln は未定義 std.stdio.foo("bar"); // エラー。foo は現在の名前空間で束縛されている std.stdio.writefln("bar"); // エラー。std は未定義 foo("bar"); // ok。foo は現在の名前空間にある // 完全修飾名は不要 io.writefln("bar"); // ok。io=std.stdio で、ioという名前が // モジュール全体を指すよう導入されている io.foo("bar"); // エラー。foo は現在の名前空間にある // io のメンバではない
モジュールのスコープ解決演算子
ローカルの名前で隠されてしまったグローバルの識別子にアクセスしたいこと、 たまにあります。 そんな時は、Dではスコープ解決演算子 '.' を使います:int x; int foo(int x) { if (y) return x; // グローバルの x ではなく foo.x を返す else return .x; // グローバルの x を返す }名前の前に'.'を付けると、モジュールスコープレベルで名前を探索することを意味します。
静的コンストラクタ・デストラクタ
静的コンストラクタ、とは、main() 関数を呼び出すより前に モジュールやクラスの初期化のために実行されるコードのことです。 静的デストラクタは、 逆にmain()の後に、モジュールが確保した システムリソースの解放などのために実行されるコードです。
一つのモジュールに複数の静的コンストラクタや静的デストラクタを定義することもできます。 静的コンストラクタはソースコードに書かれた順に上から実行され、 静的デストラクタはその逆順に実行されます。
静的コンストラクタの順序
静的初期化の順序は、各モジュールに書かれた import によって、暗黙のうちに決定されます。各モジュールは import している他のモジュールに依存していると仮定し、 被依存モジュールを先に構築するように順序が決まります。 このルールに従ってさえいれば、 その他のモジュールどうしの実行順序については特に定まっていません。
import宣言の循環(モジュールがお互いをimportしあう、循環依存)は、 どちらか一方が静的構築の不要なモジュールであれば、問題ありません。 双方とも静的構築が必要であった場合は、 実行時例外が発生します。
モジュール内部での静的構築の順序
モジュールの内部では、静的コンストラクタは、 ソースコードに書かれた順で上から実行されていきます。
静的デストラクタの順序
静的コンストラクタのちょうど逆の順番で実行される、 と定義されています。 モジュールの静的デストラクタコードは、 対応する静的コンストラクタが正しく完了していた場合にのみ実行されます。
単体テストの順序
モジュールの内部では、単体テストは、 ソースコードに書かれた順で上から実行されていきます。mixin 宣言
MixinDeclaration: mixin ( AssignExpression ) ;
AssignExpression は、 コンパイル時定数へと評価される文字列である必要があります。 その文字列の内容は正当な DeclDefs としてコンパイルできるものでなければならず、その場合、その通りにコンパイルされます。