モジュール
Module:
ModuleDeclaration DeclDefs
DeclDefs
DeclDefs:
DeclDef
DeclDef DeclDefs
DeclDef:
AttributeSpecifier
ImportDeclaration
EnumDeclaration
ClassDeclaration
InterfaceDeclaration
AggregateDeclaration
Declaration
Constructor
Destructor
UnitTest
StaticConstructor
StaticDestructor
SharedStaticConstructor
SharedStaticDestructor
ConditionalDeclaration
DebugSpecification
VersionSpecification
StaticAssert
TemplateDeclaration
TemplateMixinDeclaration
TemplateMixin
MixinDeclaration
;
モジュールはソースファイルと1対1に対応します。 モジュール名は、 ソースファイル名からパスと拡張子を除いたものになります。
モジュールは自動で、その中身をスコープとする名前空間となります。 モジュールは表面的にはクラスと似たところがありますが、別のものです:
- モジュール毎にインスタンスは1つだけで、 静的に割り当てられます。
- 仮想関数テーブルを持ちません。
- モジュールには継承や親モジュールといった概念がありません。
- 1つのファイルには1つだけモジュールが対応します。
- モジュール内のシンボルはimportできます。
- モジュールは常にグローバルスコープでコンパイルされ、 周りで宣言された属性に影響を受けることなどはありません。
モジュールは、packages と呼ばれる階層にまとめることができます。
モジュールは数々の保証を提供します:
- モジュールを 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
右端の一個前にある Identifiers は、 モジュールが属する パッケージ を指します。 パッケージはソースファイルの置かれているディレクトリに対応します。 予約語をパッケージ名にすることはできません。 従って、予約語を対応するディレクトリ名に使うこともできません。
もし ModuleDeclaration を書くならば、 ソースファイルの先頭に、一個だけ書く必要があります。
例:
module c.stdio; // これはモジュール stdio で、パッケージ c に属します
慣習として、パッケージ名とモジュール名は全て小文字で書きます。 この名前はOSのファイル名やディレクトリ名に対応していて、 多くのファイルシステムでは大文字小文字を区別しないからです。 全て小文字にすると決めておくことで、 プロジェクトを別のファイルシステムへ持っていったときの問題が少なくなります。
import 宣言
テキストをそのまま#includeするのではなく、Dでは、 ImportDeclaration によってシンボルだけをロードします:
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
ImportDeclaration には、一般化されたものからより粒度を細かくしたものまで、 いくつか種類があります。
ImportDeclaration の順番は特に意味を持ちません。
ImportDeclaration の ModuleFullyQualifiedName は、 どのパッケージに含まれているかにかかわらず、 完全修飾が必要です。 import元モジュールからの相対パスの考慮などはなされません。
基本 import (Basic 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() の呼び出し
}
公開 import (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() の呼び出し
静的 import (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 (Renamed 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 (Selective 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 のメンバではない
スコープ限定 import (Scoped Import)
import 宣言は任意のスコープで使用できます。例:
void main() {
import std.stdio;
writeln("bar");
}
このimportは、そのスコープ内での未解決シンボルを探すのに使われます。 importされたシンボルが、外部のスコープのシンボルを隠すことがあります。
関数スコープでは、import 宣言がソースコード中で現れた箇所より下でのみ、 import されたシンボルが見えるようになります。 言い方を変えると、関数スコープでの import は前方参照できません。
void main() {
void writeln(string) {}
void foo() {
writeln("bar"); // main.writeln を呼ぶ
import std.stdio;
writeln("bar"); // std.stdio.writeln を呼ぶ
void writeln(string) {}
writeln("bar"); // main.foo.writeln を呼ぶ
}
writeln("bar"); // main.writeln を呼ぶ
std.stdio.writeln("bar"); // エラー。std は未定義
}
モジュールのスコープ解決演算子
ローカルの名前で隠されてしまったグローバルの識別子にアクセスしたいこと、 たまにあります。 そんな時は、Dではスコープ解決演算子 ‘.’ を使います:int x;
int foo(int x)
{
if (y)
return x; // グローバルの x ではなく foo.x を返す
else
return .x; // グローバルの x を返す
}
名前の前に ‘.’ を付けると、モジュールスコープレベルで名前を探索することを意味します。
静的コンストラクタ・デストラクタ
静的コンストラクタ、とは、main() 関数を呼び出すより前に モジュールやクラスの初期化のために実行されるコードのことです。 静的デストラクタは、 逆にmain()の後に、モジュールが確保した システムリソースの解放などのために実行されるコードです。
一つのモジュールに複数の静的コンストラクタや静的デストラクタを定義することもできます。 静的コンストラクタはソースコードに書かれた順に上から実行され、 静的デストラクタはその逆順に実行されます。
静的コンストラクタや静的デストラクタはスレッドローカル記憶域に対して実行され、 スレッドが生成/破棄されるたびに実行されます。
shared静的コンストラクタやshared静的デストラクタはグローバル記憶域に対して実行され、 プログラムの開始時と終了時に、 一度ずつだけ実行されます。
静的コンストラクタの順序
各モジュールのshared静的コンストラクタは、 どの静的コンストラクタよりも前に実行されます。
静的初期化の順序は、各モジュールに書かれた import によって、暗黙のうちに決定されます。各モジュールは import している他のモジュールに依存していると仮定し、 被依存モジュールを先に構築するように順序が決まります。 このルールに従ってさえいれば、 その他のモジュールどうしの実行順序については特に定まっていません。
import宣言の循環(モジュールがお互いをimportしあう、循環依存)は、 どちらか一方が静的構築の不要なモジュールであれば、問題ありません。 双方とも静的構築が必要であった場合は、 実行時例外が発生します。
モジュール内部での静的構築の順序
モジュールの内部では、静的コンストラクタは、 ソースコードに書かれた順で上から実行されていきます。静的デストラクタの順序
静的コンストラクタのちょうど逆の順番で実行される、 と定義されています。 モジュールの静的デストラクタコードは、 対応する静的コンストラクタが正しく完了していた場合にのみ実行されます。
shared静的デストラクタは全ての静的デストラクタより後に実行されます。
単体テストの順序
モジュールの内部では、単体テストは、 ソースコードに書かれた順で上から実行されていきます。mixin 宣言
MixinDeclaration:
mixin ( AssignExpression ) ;
AssignExpression は、 コンパイル時定数へと評価される文字列である必要があります。 その文字列の内容は正当な DeclDefs としてコンパイルできるものでなければならず、その場合、その通りにコンパイルされます。