モジュールとライブラリ
Dプログラム(およびライブラリ)の構成要素はモジュールだ。
Dのモジュールはシンプルな概念に基づいている:すべてのソースファイルがモジュールだ。したがって、これまでプログラムを書いてきた単一のファイルはすべて個別のモジュールだった。
デフォルトでは、モジュールの名前はファイル名から.d
拡張子を削除したものと同一だ。明示的に指定する場合、モジュールの名前はmodule
キーワードで定義され、ソースファイルの最初の非コメント行に記述する必要がある。
例えば、ソースファイルの名前が"cat.d"である場合、モジュールの名前はmodule
キーワードで指定する。
module
行は、モジュールがどのパッケージにも属していない場合(以下を参照)は省略可能だ。指定しない場合、.d
拡張子を除いたファイル名と同じになる。
static this()
static ~this()
static this()
static ~this()
は、モジュールスコープでは、struct
とclass
の対応するキーワードと同様に機能する:
これらのスコープにあるコードは、スレッドごとに1回ずつ実行される。(ほとんどのプログラムは、main()
関数の実行を開始する単一のスレッドで構成されていることに注意。)プログラム全体で1回だけ実行すべきコード (shared
およびimmutable
変数の初期化など)は、shared static this()
およびshared static ~this()
ブロックで定義する必要がある。これについては、データ共有の並行処理の章で説明する。
ファイル名とモジュール名
Dは、ソースコードおよびモジュール名においてUnicodeをサポートしている。ただし、ファイルシステムのUnicodeサポートはさまざまだ。例えば、ほとんどのLinuxファイルシステムはUnicodeをサポートしているが、Windowsファイルシステムではファイル名の大文字と小文字が区別されない場合がある。さらに、ほとんどのファイルシステムでは、ファイル名およびディレクトリ名に使用できる文字が制限されている。
移植性を考慮して、ファイル名には小文字のASCII文字のみを使用することをお勧めする。例えば、Résumé
というクラスには、"resume.d"というファイル名が適している。
したがって、モジュールの名前もASCII文字で構成される:
パッケージ
関連するモジュールの組み合わせをパッケージと呼ぶ。Dパッケージもシンプルな概念だ:同じディレクトリ内に存在するソースファイルは、同じパッケージに属するとみなされる。ディレクトリ名はパッケージ名となり、モジュール名の最初の部分としても指定する必要がある。
例えば、"cat.d"と"dog.d"が"animal"ディレクトリ内にある場合、モジュール名とともにディレクトリ名を指定すると、それらは同じパッケージの一部になる。
同様に、dog
モジュールの場合:
パッケージの一部であるモジュールの場合、module
行は省略できず、パッケージ名を含むモジュール名全体を指定する必要がある。
パッケージ名はディレクトリ名に対応しているため、1 ディレクトリレベルより深いモジュールは、その階層構造をパッケージ名に反映させる必要がある。例えば、"animal"ディレクトリに"vertebrate"ディレクトリが含まれている場合、そのディレクトリ内のモジュールの名前には"vertebrate
"も含まれる。
ディレクトリ階層は、プログラムの必要性に応じて任意に複雑にすることができる。比較的短いプログラムでは、通常、すべてのソースファイルが1つのディレクトリに格納される。
モジュールのインポート
これまでほとんどのプログラムで使用してきたimport
キーワードは、現在のモジュールにモジュールを導入するために使用される:
モジュール名には、パッケージ名も含めることができる。例えば、上記のstd.
は、stdio
がstd
パッケージの一部であるモジュールであることを示す。
animal.cat
モジュールとanimal.dog
モジュールは、同様にインポートされる。次のコードが"deneme.d"というファイル内にあると仮定しよう。
注釈:下記で説明するように、プログラムを正しく構築するには、これらのモジュールファイルもリンカに提供する必要がある。
複数のモジュールを同時にインポートすることもできる。
選択的なインポート
モジュールをその名前すべてでインポートする代わりに、そのモジュールから特定の名前だけをインポートすることもできる。
上記のコードは、writeln
だけがインポートされており、writefln
はインポートされていないため、コンパイルできない。
選択的なインポートは、名前衝突の可能性を減らすことができるため、モジュール全体をインポートするよりも望ましいと考えられている。以下の例でわかるように、同じ名前が複数のインポートされたモジュールに存在する場合、名前衝突が発生する可能性がある。
選択的インポートでは、コンパイラは実際にインポートされるモジュールの一部だけをコンパイルすればよいため、コンパイル時間も短縮される。一方、選択的インポートでは、インポートする名前をすべてimport
行で個別に指定する必要があるため、作業量が増える。
この本では、主に簡潔にするため、選択的インポートは使用していない。
ローカルインポート
これまで、必要なモジュールは常にプログラムの先頭でインポートしてきた。
しかし、モジュールはソースコードの他の行でもインポートすることができる。例えば、次のプログラムの2つの関数は、それぞれ必要なモジュールを自分のスコープにインポートしている。
ローカルインポートは、すべてのモジュールを無条件に先頭にインポートするよりも、実際に使用されるスコープにあるモジュールだけをインポートできるため、グローバルインポートよりも推奨される。コンパイラが、プログラムが関数を呼び出さないことを認識している場合、その関数内のインポート指令を無視することができる。
さらに、ローカルにインポートされたモジュールは、そのローカルスコープ内でのみアクセス可能なので、名前の衝突のリスクがさらに低くなる。
後でミックスインの章で、テンプレートミックスインにはローカルインポートが実際に必要であることを説明する。
この本全体の例では、ローカルインポートを利用していない。これは、主に、ローカルインポートがこの本の執筆開始後にDに追加されたためだ。
モジュールの位置
コンパイラは、パッケージ名とモジュール名を直接ディレクトリ名とファイル名に変換して、モジュールファイルを探す。
例えば、前の2つのモジュールは、それぞれ"animal/cat.d"および"animal/dog.d"という場所にある (ファイルシステムによっては"animal\cat.d"および"animal\dog.d"になる)。メインのソースファイルも考慮すると、上記のプログラムは3つのファイルで構成されている。
長いモジュール名と短いモジュール名
プログラム内で使用される名前は、モジュール名とパッケージ名を含めて表記されることがある:
通常、長い名前は必要ないが、名前の衝突が発生する場合がある。例えば、複数のモジュールに存在する名前を参照する場合、コンパイラはどちらの名前を指すのか判断できない。
次のプログラムでは、2つの別々のモジュールで定義されている2つのJaguar
構造体を区別するために、長い名前をすべて表記している。animal
とcar
:
インポートの名前変更
便宜上、または名前の競合を解決するために、インポートされたモジュールの名前を変更することができる。
インポート全体を名前変更する代わりに、インポートされた個々のシンボルを名前変更することもできる。
例えば、次のコードを-w
コンパイラスイッチでコンパイルすると、コンパイラは、.sort
プロパティの代わりにsort()
関数を使用すべきだと警告する。
注釈:上記のarr.sort
式は、sort(arr)
と同等だが、後で説明するUFCS構文で記述されている。
この場合の解決策の1つは、std.algorithm.sort
を名前変更してインポートすることだ。以下の新しい名前algSort
は、 sort()
関数を意味し、コンパイラの警告は表示されなくなる。
パッケージをモジュールとしてインポートする
パッケージの複数のモジュールを一緒にインポートする必要がある場合がある。例えば、animal
パッケージの1つのモジュールをインポートするたびに、他のすべてのモジュールもインポートする必要がある場合がある。animal.cat
、animal.dog
、animal.horse
などだ。
このような場合、パッケージをモジュールとしてインポートすることで、パッケージのモジュールの一部またはすべてをインポートすることができる。
これは、パッケージディレクトリにある特別な設定ファイルによって実現される。このファイルは、必ずpackage.d
という名前で保存。この特別なファイルには、パッケージのmodule
ディレクティブが含まれており、パッケージのモジュールを公にインポートする。
モジュールを公開してインポートすると、そのモジュールはインポートしたモジュールのユーザーも使用できるようになる。その結果、ユーザーはanimal
モジュール (実際にはパッケージ)だけをインポートしても、animal.cat
およびその他のすべてのモジュールにアクセスできるようになる。
機能の廃止
モジュールは時間とともに進化し、新しいバージョン番号でリリースされる。特定のバージョン以降、モジュールの作者は一部の機能を非推奨にする場合がある。機能を非推奨にするとは、新規に作成されるプログラムがその機能に依存しないようにすることである。非推奨の機能を使用することは推奨されない。非推奨の機能は、将来的にモジュールから削除される可能性がある。
機能が廃止される理由はたくさんある。例えば、モジュールの新しいバージョンにより、より優れた代替機能が追加された、機能が別のモジュールに移動された、モジュール内の他の部分との整合性を保つために機能名が変更された、などである。
機能の廃止は、deprecated
属性で定義することで正式になる。必要に応じて、カスタムメッセージも指定できる。例えば、次の廃止メッセージは、関数の名前が変更されたことをユーザーに伝える。
モジュールのユーザーは、次のコンパイラスイッチのいずれかを指定することで、廃止された機能が使用された際にコンパイラがどのように反応するかを決定できる:
例えば、プログラムで廃止された機能を呼び出し、-de
でコンパイルすると、コンパイルは失敗する。
廃止予定の機能の名前は、通常、新しい名前のalias
として定義される:
alias
キーワードについては、後の章で説明する。
プログラムにモジュール定義を追加する
import
キーワードだけでは、モジュールをプログラムの一部として機能させることはできない。これは単に、現在のモジュール内でモジュールの機能を有効にするだけだ。これだけの機能は、コードをコンパイルするだけで十分だ。
メインソースファイル"deneme.d"のみでは、以前のプログラムをビルドすることはできない。
これらのエラーメッセージはリンカーによって生成される。ユーザーフレンドリーなメッセージではないが、プログラムに必要な定義が欠落していることを示している。
プログラムの実際のビルドはリンカーの責任で、コンパイラが裏で自動的に呼び出す。コンパイラは、先ほどコンパイルしたモジュールをリンカーに渡し、リンカーはそれらのモジュール(とライブラリ)を結合して実行可能プログラムを生成する。
そのため、プログラムを構成するすべてのモジュールをリンカーに提供する必要がある。上記のプログラムをビルドするには、コンパイルラインに"animal/cat.d"と"animal/dog.d"も指定する必要がある:
コマンドラインで毎回モジュールを個別に指定する代わりに、それらをライブラリとして結合することができる。
ライブラリ
コンパイルされたモジュールの集まりは、ライブラリと呼ばれる。ライブラリはそれ自体ではプログラムではなく、main()
関数を持っていない。ライブラリには、関数、構造体、クラス、およびモジュールのその他の機能のコンパイル済み定義が含まれており、これらは後でリンカによってリンクされ、プログラムが生成される。
dmdの-lib
コマンドラインオプションは、ライブラリを作成するために使用される。次のコマンドは、"cat.d"と"dog.d"モジュールを含むライブラリを作成する。ライブラリの名前は、-of
スイッチで指定される:
ライブラリファイルの実際の名前は、プラットフォームによって異なる。例えば、Linuxシステムでは、ライブラリファイルの拡張子は.a
だ。animal.a
。
このライブラリが構築されると、個別に"animal/cat.d"と"animal/dog.d"モジュールを指定する必要はなくなる。ライブラリファイルだけで十分だ:
上記のコマンドは、以下のコマンドを置き換える:
例外として、Dの標準ライブラリPhobosはコマンドラインで指定する必要はない。このライブラリは、バックグラウンドで自動的にインクルードされる。それ以外の場合は、次のように指定する。
注釈:Phobosライブラリの名前と場所は、システムによって異なる場合がある。
他の言語のライブラリの使用
Dは、CやC++などの他のコンパイル言語で記述されたライブラリを使用することができる。ただし、言語によってリンクが異なるため、そのようなライブラリはDコードからはDバインディングを介してのみ利用可能だ。
リンクは、ライブラリ内のエンティティのアクセス可能性を決定するルールセットであり、それらのエンティティの名前(シンボル)がコンパイル済みコードでどのように表されるかを決定する。コンパイル済みコード内の名前は、プログラマーがソースコードに書く名前とは異なる:コンパイル済み名前は、特定の言語またはコンパイラのルールに従って名前がマングリングされる。
例えば、Cのリンクでは、C関数名foo
は、コンパイルされたコードでは、先頭にアンダースコアが付いて_foo
とマングルされる。C++やDなどの言語では、異なるモジュール、構造体、クラスなどで異なるエンティティに同じ名前を使用したり、関数をオーバーロードしたりすることができるため、名前のマングルはより複雑になる。ソースコードでfoo
という名前のD関数は、プログラム内に存在する他のすべてのfoo
という名前と区別できるように、マングリングされる必要がある。通常、正確なマングリング名はプログラマーにとって重要ではないが、core.demangle
モジュールを使用して、シンボルをマングリングおよびデマングリングすることができる。
注釈: mangle()
は関数テンプレートであり、この時点ではその構文は不明だ。テンプレートについては、テンプレートに関する章で後で説明する。
上記のfoo
と同じ型で、deneme.foo
という名前を持つ関数は、コンパイルされたコードでは次のようにマングリングされる。
_D6deneme3fooFZv
名前マングリングは、リンカーのエラーメッセージにユーザーフレンドリーな名前を含めることができない理由である。例えば、上記のエラーメッセージのシンボルは、animal.cat.Cat
ではなく_D6animal3cat3Cat7__ClassZ
であった。
extern()
属性は、エンティティのリンクを指定する。extern()
で使用できる有効なリンク型は、C
、C++
、D
、Objective-C
、Pascal
、System
、およびWindows
である。例えば、DコードでCライブラリで定義されている関数を呼び出す必要がある場合、その関数は C リンクとして宣言する必要がある。
C++ リンクの場合、名前が定義されている名前空間は、extern()
属性の2番目の引数として指定される。例えば、次の宣言では、bar()
はC++ライブラリで定義されている関数a::b::c::bar()
の宣言である(Dコードではコロンではなくドットを使用していることに注意)。
外部ライブラリの機能のD宣言を含むファイルは、そのライブラリのDバインディングと呼ばれる。幸いなことに、多くの場合、プログラマーはこれらを最初から作成する必要はない。なぜなら、多くの人気のある非DライブラリのDバインディングがDeimosプロジェクトを通じて利用可能だからだ。
リンク型を使用しない場合、extern
属性は別の意味を持つ。これは、変数のストレージは外部ライブラリの責任であり、Dコンパイラはモジュール内にそのためのスペースを予約してはならないことを指定する。extern
とextern()
は意味が異なるため、一緒に使用することができる。
上記でextern
属性が指定されていない場合、Cリンクを持つg_variable
は、このDモジュールの変数になる。