英語版
このページの英語版を見る

モジュール

Module:
    ModuleDeclaration
    ModuleDeclaration DeclDefs
    DeclDefs
DeclDefs: DeclDef DeclDef DeclDefs
DeclDef: AttributeSpecifier Declaration Constructor Destructor Postblit Invariant UnitTest AliasThis StaticConstructor StaticDestructor SharedStaticConstructor SharedStaticDestructor ConditionalDeclaration DebugSpecification VersionSpecification MixinDeclaration EmptyDeclaration
EmptyDeclaration: ;

モジュールはソースファイルと一対一で対応している。 ModuleDeclarationで明示的に設定されていない場合、モジュール名は パスと拡張子を除いたファイル名となる。

モジュールの名前は、その内容に対して自動的に名前空間スコープとして機能する。モジュールは 表面的にはクラスに似ているが、次のような点で異なる。

モジュールはパッケージと呼ばれる階層にグループ化することができる。

モジュールは、いくつかの保証を提供する。

モジュール宣言

ModuleDeclarationは、モジュール名とそれが属するパッケージを設定する。 ModuleDeclarationが存在しない場合、モジュール名はソースファイル名と同じ名前( パスと拡張子を除いたもの)とみなされる。

ModuleDeclaration:
    ModuleAttributesopt module ModuleFullyQualifiedName ;
ModuleAttributes: ModuleAttribute ModuleAttribute ModuleAttributes
ModuleAttribute: DeprecatedAttribute UserDefinedAttribute
ModuleFullyQualifiedName: ModuleName Packages . ModuleName
ModuleName: Identifier
xml-ph-0000@deepl.internal
Packages:
    PackageName
    Packages . PackageName
PackageName: Identifier

右端の識別子の前の識別子は、モジュールが含まれるパッケージである。 パッケージはソースファイルのパスにあるディレクトリ名に対応する。 パッケージ名とモジュール名はキーワードにすることはできない。

ModuleDeclarationが存在する場合、それはソースファイル内で最初で唯一の宣言でなければならず、 コメントと#line ディレクティブのみが先行できる。

例:

module c.stdio; // cパッケージのモジュールstdio

慣例として、パッケージ名およびモジュール名はすべて小文字で表記する。これは、 これらの名前がオペレーティングシステムのディレクトリおよびファイル名と1対1で対応しており、 多くのファイルシステムでは大文字と小文字の区別がないためである。 パッケージ名およびモジュール名をすべて小文字で表記することで、異なるファイルシステム間でプロジェクトを移動する際の問題を回避または最小限に抑えることができる。

モジュールのファイル名が無効なモジュール名である場合(例えばfoo-bar.d )、モジュール宣言を使用して有効なモジュール名を設定する。

module foo_bar;
Implementation Defined:
  1. パッケージおよびモジュール識別子をディレクトリ名およびファイル名にマッピングする。
Best Practices: PackageNamesとModuleNamesは、ASCII文字(小文字、数字、またはxml-ph-0000@deepl.internal)で構成し、最大限の移植性とさまざまなファイルシステムとの互換性を確保する。
  1. PackageNamesModuleNamesは、ASCII文字 (小文字、数字、または_ )で構成し、さまざまなファイルシステムとの最大限の互換性と移植性を確保する。
  2. パッケージおよびモジュールのファイル名は、 ASCII小文字、数字、および_のみで構成され、キーワードであってはならない。

非推奨のモジュール

ModuleDeclarationには オプションでDeprecatedAttributeを指定できる。コンパイラは、非推奨のモジュールがインポートされた際にメッセージを表示する。

deprecated module foo;
module bar;
import foo;  // 非推奨: モジュールfooは非推奨である

DeprecatedAttributeには、より詳細な情報を提供するためのオプションのAssignExpression引数がある。 AssignExpressionは、コンパイル時に文字列として評価されなければならない。

deprecated("Please use foo2 instead.")
module foo;
module bar;
import foo;  // 非推奨: モジュールfooは非推奨 - 代わりにfoo2を使ってほしい。
Implementation Defined:
  1. 非推奨メッセージがユーザーにどのように表示されるか。

インポート宣言

あるモジュール内のシンボルを別のモジュールで利用できるようにするには、 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のModuleFullyQualifiedNamesは、 それが含まれるパッケージをすべて指定して完全に修飾しなければならない。 それらは、インポートするモジュールに対して相対的なものではない。

ImportDeclarationのModuleFullyQualifiedNamesは、 それが含まれるパッケージをすべて含めて完全に修飾しなければならない。これらは、 インポートするモジュールに対して相対的なものではない。

シンボル名検索

最も単純なインポートの方法は、インポートされるモジュールを列挙することである。

module myapp.main;

import std.stdio; // stdパッケージからモジュールstdioをインポートする

class Foo : BaseClass
{
    import myapp.foo;  // このクラスのスコープで、モジュールmyapp.fooをインポートする
    void bar ()
    {
        import myapp.bar;  // この関数のスコープで、モジュールmyapp.barをインポートする
        writeln("hello!");  // std.stdio.writelnを呼び出す
    }
}

修飾されていないシンボル名が使用された場合、2段階の検索が実行される。 まず、最も内側のスコープから開始して、モジュールスコープが検索される。 例えば、前述の例でwriteln を検索する際には、 次の順序で検索される。

最初の検索が成功しなかった場合、2回目の検索がインポートに対して実行される。 2回目の検索フェーズでは継承されたスコープは無視される。これには ベースクラスやインターフェースのスコープ(この例では、BaseClass のインポートは 無視される)、および混合インポートtemplate のインポートも含まれる。

シンボル検索は、一致するシンボルが見つかるとすぐに終了する。 同じ名前のシンボルが同じ検索フェーズで2つ見つかった場合、この曖昧性は コンパイルエラーとなる。

module A;
void foo();
void bar();
module B;
void foo();
void bar();
module C;
import A;
void foo();
void test()
{
    foo(); // C.foo()が呼び出され、インポートが検索される前に見つかる
    bar(); // インポートが検索されるので、A.bar()が呼び出される
}
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 foo = B.foo;
void test()
{
    foo();   // B.foo()を呼び出す
    A.foo(); // A.foo()を呼び出す
    B.foo(); // B.foo()を呼び出す
}

Public Imports

デフォルトでは、インポートはプライベートである。つまり、モジュールAが モジュールBをインポートし、モジュールBがモジュールCをインポートしている場合、C内の名前はB内でのみ可視であり、 A内では不可視である。

インポートは明示的にパブリックとして宣言することができ、 その場合、インポートされたモジュール内の名前は、さらにインポートされたモジュールからも見えるようになる。したがって、 モジュールAがモジュールBをインポートする上記の例において、モジュールBモジュールCをパブリックにインポートする場合、 Cの名前はAからも見えるようになる。

公開的にインポートされたモジュール内のすべてのシンボルは、インポート元のモジュール内でもエイリアスとして参照される。 したがって、上記の例でCがfooという名前を含んでいる場合、 AからはfooB.fooC.foo としてアクセス可能となる。

別の例:

module W;
void foo() { }
module X;
void bar() { }
module Y;
import W;
public import X;
...
foo();  // W.foo()を呼び出す
bar();  // X.bar()を呼び出す
module Z;
import Y;
...
foo();   // エラー、foo()は未定義である
bar();   // OK、X.bar()を呼び出す
X.bar(); /// ditto
Y.bar(); // OK、Y.bar()はX.bar()のエイリアスである

静的インポート

静的インポートでは、モジュールの名前を参照するには完全修飾名を使用する必要がある。

static import std.stdio;

void main()
{
    writeln("hello!");           // エラー、writelnは未定義である
    std.stdio.writeln("hello!"); // OK、writelnは完全修飾である
}

名前を変更したインポート

インポートにローカル名を付けることができ、その場合、 モジュールのシンボルへのすべての参照は、以下のように修飾する必要がある。

import io = std.stdio;

void main()
{
    io.writeln("hello!");        // OK、std.stdio.writelnを呼び出す
    std.stdio.writeln("hello!"); // エラー、stdは未定義である
    writeln("hello!");           // エラー、writelnは未定義である
}
Best Practices: インポート名の変更は、非常に長いインポート名を扱う際に便利です。

選択的インポート

特定のシンボルをモジュールから排他的にインポートし、現在の名前空間にバインドすることができます。

import std.stdio : writeln, foo = write;

void main()
{
    std.stdio.writeln("hello!"); // エラー、stdは未定義である
    writeln("hello!");           // OK、writelnは現在の名前空間にバインドされる
    write("world");              // エラー、writeは未定義である
    foo("world");                // OK、std.stdio.write()を呼び出す
    fwritefln(stdout, "abc");    // エラー、fwriteflnは未定義である
}

static 選択的インポートでは使用できません。

名前の変更と選択的インポート

リネームと選択的インポートを組み合わせる場合:

import io = std.stdio : foo = writeln;

void main()
{
    writeln("bar");           // エラー、writelnは未定義である
    std.stdio.foo("bar");     // エラー、fooは現在の名前空間にバインドされている
    std.stdio.writeln("bar"); // エラー、stdは未定義である
    foo("bar");               // OK、fooは現在の名前空間にバインドされている、
                              // FQNは必要ない
    io.writeln("bar");        // OK、io=std.stdioはioという名前にバインドされているモジュール全体を
                              // 参照するために、現在の名前空間にioという名前をバインドした
                              // モジュール全体を参照する
    io.foo("bar");            // エラー、fooは現在の名前空間にバインドされている、
                              // fooはioのメンバではない
}

スコープ付きインポート

インポート宣言はあらゆるスコープで使用できる。例えば:

void main()
{
    import std.stdio;
    writeln("bar");
}

そのスコープで未解決のシンボルを満たすためにインポートが参照される。 インポートされたシンボルは、外部スコープのシンボルを隠す可能性がある。

関数スコープでは、インポートされたシンボルは、インポート宣言が 関数本体に()()的に現れた後にのみ可視となる。言い換えれば、 関数スコープにおけるインポートされたシンボルは前方参照できない。

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は未定義である
}

モジュールスコープ演算子

先頭のドット (.) は、 識別子をモジュールスコープで検索することを意味する。

const int x = 1;

void main()
{
    int x = 5;
    assert(x == 5); // グローバルのxではなく、main.x
    assert(.x == 1); // グローバルのx
}
xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal

静的構築と破棄

静的コンストラクタはモジュールの状態を初期化するために実行される。 静的デストラクタはモジュールの状態を終了させる。

モジュールには複数の静的コンストラクタと静的デストラクタが存在してもよい。 静的コンストラクタは()()順に実行され、静的デストラクタは 逆()()順に実行される。

共有でない静的コンストラクタおよびデストラクタは、 メインスレッドを含むスレッドが作成または破棄されるたびに実行される。

共有静的コンストラクタは、main() が呼び出される前に一度実行される。 共有静的デストラクタは、main() 関数が返された後に実行される。

import resource;

Resource x;
shared Resource y;
__gshared Resource z;

static this()  // 共有でない静的コンストラクタ
{
    x = acquireResource();
}

shared static this()  // 共有の静的コンストラクタ
{
    y = acquireSharedResource();
    z = acquireSharedResource();
}

static ~this()  // 共有でない静的デストラクタ
{
    releaseResource(x);
}

shared static ~this()   // 共有のスタティックデストラクタ
{
    releaseSharedResource(y);
    releaseSharedResource(z);
}
Best Practices:
  1. 共有静的コンストラクタとデストラクタは、共有グローバルデータの初期化と終了に使用される。
  2. 共有でない静的コンストラクタおよびデストラクタは、スレッドローカルデータの初期化と終了に使用される。

静的コンストラクションの順序

すべてのモジュールで共有される静的コンストラクタは、共有でない静的コンストラクタよりも先に実行される。

静的初期化の順序は、各モジュールにおけるインポート宣言によって暗黙的に決定される。各モジュールは、 インポートされたモジュールが静的に最初に構築されることに依存していると想定される。 モジュールの静的コンストラクタの実行には、これ以外の順序は課されていない。

インポート宣言における循環(循環依存)は、 どちらのモジュールにも静的コンストラクタまたは静的デストラクタが含まれていない、あるいはどちらか一方のみに含まれている場合は許可される。 このルールに違反すると、実行時例外が発生する。

Implementation Defined: 実装では、サイクル検出による中断をオーバーライドする手段を提供できる。典型的な方法では、Dランタイムスイッチを使用するxml-ph-0000@deepl.internal。これにより、以下の動作がサポートされる。
  1. 実装では、サイクル検出による中断をオーバーライドする手段を提供することができる。 一般的な方法では、Dランタイムスイッチを使用する--DRT-oncycle=... 。以下の動作がサポートされる。
    1. abort デフォルトの動作。前述のセクションで説明されている通常の動作 。
    2. print 検出されたすべてのサイクルをプリントするが、実行は中断しない。 サイクルが存在する場合、静的構造の構築順序は 処理系定義であり、有効であることは保証されない。
    3. ignore 実行を中断したり、サイクルを印刷したりしない。 サイクルが存在する場合、静的構造の構築順序は処理系定義であり、 有効であることは保証されない。
    valid有効
xml-ph-0000@deepl.internal
Best Practices: 循環インポートは可能な限り避けるべきである。これは、プログラムの構造が独立したモジュールに適切に分解されていないことを示す兆候である。互いにインポートする2つのモジュールは、循環を伴わない3つのモジュールに再構成できることが多い。
  1. 循環インポートは可能な限り避けるべきである。これは、 プログラムの構造が独立したモジュールに適切に分解されていないことを示す兆候である。 互いにインポートし合う2つのモジュールは、循環を伴わない3つのモジュールに再構成できることが多い。 3番目のモジュールには、他の2つのモジュールに必要な宣言が含まれる。
xml-ph-0000@deepl.internal

モジュール内の静的構築の順序

モジュール内では、静的コンストラクションは、それらが現れる()()順に発生する。

静的破壊の順序

これは、静的構築の順序とまったく逆の順序である。 個々のモジュールの静的デストラクタは、 対応する静的コンストラクタが正常に完了した場合のみ実行される。

共有静的デストラクタは静的デストラクタの後に実行される。

ユニットテストの順序

ユニットテストは、モジュール内に現れる()()順に実行される。

ミックスイン宣言

MixinDeclaration:
    mixin ( ArgumentList ) ;

ArgumentList内の 各AssignExpressionは コンパイル時に評価され、結果は文字列として表現可能でなければならない。 結果の文字列は結合されて1つの文字列が形成される。 文字列のテキスト内容は、有効なDeclDefとしてコンパイル可能でなければならず、 実際そのようにコンパイルされる。

ミックスインの内容は、同じスコープの他のDeclDefによって前方参照することはできない。 これは、まだASTに読み込まれていないためである。

class B : A {}      // エラー: 未定義の識別子`A`が返される
mixin ("class A {}");

前方参照は、宣言の後で処理されるため、関数本体でのみ機能する可能性がある。 xml-ph-0000@deepl.internal

void v()
{
    class B : A {}
}
mixin ("class A {}");

パッケージモジュール

パッケージモジュールを使用すると、他のモジュールを公開でインポートすることができ、 よりシンプルなインポート構文が提供される。これにより、モジュールを モジュール群のパッケージに変換することが可能となり、そのモジュールを使用している既存のコードを壊すことなく実行できる。 ライブラリモジュール群の例:

libweb/client.d:
module libweb.client;

void runClient() { }
libweb/server.d:
module libweb.server;

void runServer() { }
libweb/package.d:
module libweb;

public import libweb.client;
public import libweb.server;

パッケージモジュールのファイル名はpackage.d でなければならない。モジュール名は パッケージの完全修飾名として宣言される。パッケージモジュールは 他のモジュールと同様にインポートできる。

test.d:
module test;

// パッケージモジュールをインポートする
import libweb;

void main()
{
    runClient();
    runServer();
}

パッケージモジュールはサブパッケージ内にネストすることができる。

libweb/utils/package.d:
// 'utils'だけでなく、パッケージの完全修飾名で宣言しなければならない
module libweb.utils;

// 'libweb.utils'パッケージ内のモジュールを一般にインポートする。
public import libweb.utils.conv;
public import libweb.utils.text;

パッケージモジュールは、標準モジュールのインポート宣言でインポートできる。

test.d:
module test;

// パッケージモジュールをインポートする
import libweb.utils;

void main() { }