この記事は DMD 2.078.0 Has Been Released – The D Blog を自分用に翻訳したものを 許可を得て 公開するものである。
ソース中にコメントの形で原文を残している。 誤字や誤訳など気になったら Pull requestを投げつけて くれると喜ぶ。
DMDのメジャーリリース、2.078.0が新年にパッケージされ、提供されていました。 完全なチェンジログはdlang.orgで見ることができ、あなたのプラットフォーム向けのコンパイラがメインダウンロードページか2.078.0 リリースディレクトリからダウンロードできます。
このリリースでは複数のQoLの改善、軽微な問題や矛盾の修正が提供され、そのうち3つはDRuntimeを使わずにDでプログラミングをする際の問題を解決するためのものです。
Cランタイムのコンストラクションとデストラクション
Dには静的コンストラクタ/デストラクタがあり、大抵aggregate typeのメンバとしてや、モジュールレベルに存在します。
コンストラクタはDRuntimeの初期化ルーチンの間にレキシカルオーダーで呼ばれ、デストラクタはランタイムが終了する時に逆順に呼ばれます。
しかし-betterCコンパイラスイッチを使うなどしてDRuntimeのない環境でプログラミングをする時や、ランタイムが途中でなくなった時には、静的コンストラクションとデストラクションができなくなります。
DMD 2.078.0はそれらの環境に静的モジュールコンストラクションとデストラクションを、それぞれpragma(crt_constructor)、pragma(crt_destructor)という新しい2つのプラグマの形で導入します。
前者は関数をCのmainよりも前に、後者はCのmainよりも後に実行させます。たとえばこのように:
crun1.d
// コンパイル方法: dmd crun1.d
// または: dmd -betterC crun1.d
import core.stdc.stdio;
// 以下すべての関数はC リンケージ(cdecl)
// になります。
extern(C):
pragma(crt_constructor)
void init()
{
puts("init");
}
pragma(crt_destructor)
void fini()
{
puts("fini");
}
void main()
{
puts("C main");
}
新しいプラグマのついた関数はextern(C)リンケージ属性で宣言される必要があります。
この例ではmainも、必須ではありませんがextern(C)として宣言されています。
8行目のコロンによってそれ以降、モジュールの終わりか新しいリンケージ属性が適用されるまでのすべての関数に属性が適用されます。
通常のDのプログラムでは、CのmainはDRuntimeのエントリポイントでありコンパイラによって生成されます。
CのランタイムがCのmainを呼ぶと、DのランタイムはGCの開始、静的コンストラクタの実行、コマンドライン引数の文字列配列化、アプリケーションのmain関数、つまりDのmainの呼び出しなどの初期化を行います。
Dのモジュールのmainにextern(C)がついている時はコンパイラがCのmain関数を生成しないため、DRuntimeの実装が根本的に置き換えられます。
-betterCがコマンドラインに与えられていない、または置き換え実装が提供されていない場合、DRuntimeは利用可能で手動で初期化/終了できます。
上の例は明らかにcrt_constructorプラグマがinitの実行をCのmainよりも前に発生させ、crt_destructorがfiniを後に実行させることを示しています。
これによってDRuntimeが使えない時の新たな選択肢が導入されます。
しかし、mainからextern(C)を取り除き、同じコマンドラインを実行すると:
crun2.d
// コンパイル方法: dmd crun2.d
import core.stdc.stdio;
pragma(crt_constructor)
extern(C) void init()
{
puts("init");
}
pragma(crt_destructor)
extern(C) void fini()
{
puts("fini");
}
void main()
{
import std.stdio : writeln;
writeln("D main");
}
CのmainはDRuntimeに属し、mainはDのmainであるという点が異なります。
実行順はinit、Cのmain、Dのmain、finiとなります。
つまり事実上initはDRuntimeが初期化される前に、finiは終了した後に呼ばれます。
この例はDRuntimeの関数writelnを使うため、-betterCではコンパイルできません。
writelnをモジュールの1番上でインポートし、putsと置き換えると動作することに気づいたかもしれません。
しかしDRuntimeが使えたとしても、crt_constructorやcrt_destructorが実行されるときは有効ではないことを忘れないでください。
-betterCのRAII
-betterCモードの制限のひとつにRAIIの欠如がありました。
通常のDのコードでは、structのデストラクタはインスタンスがスコープから出た時に実行されます。
これはDRuntimeによって行われ、そして-betterCモードではそのランタイムが使えないため、structのデストラクタもまた使えません。
DMD 2.078.0で、それは過去の話になります。
destruct.d
// コンパイル方法: dmd -betterC destruct.d
import core.stdc.stdio : puts;
struct DestroyMe
{
~this()
{
puts("Destruction complete.");
}
}
extern(C) void main()
{
DestroyMe d;
}
面白いことに、これはtry..finallyの文脈で実装されており、そのため副作用として-betterCモードがtry、finally ブロックをサポートするようになりました:
cleanup1.d
// コンパイル方法: dmd -betterC cleanup1.d
import core.stdc.stdlib,
core.stdc.stdio;
extern(C) void main()
{
int* ints;
try
{
// ここでリソースを取得
ints = cast(int*)malloc(int.sizeof * 10);
puts("Allocated!");
}
finally
{
// ここでリソースを開放
free(ints);
puts("Freed!");
}
}
Dのscope(exit)機能もtry..finallyの文脈で実装されているため、これも-betterCモードで使うことができます:
cleanup2.d
// コンパイル方法: dmd -betterC cleanup2.d
import core.stdc.stdlib,
core.stdc.stdio;
extern(C) void main()
{
auto ints1 = cast(int*)malloc(int.sizeof * 10);
scope(exit)
{
puts("Freeing ints1!");
free(ints1);
}
auto ints2 = cast(int*)malloc(int.sizeof * 10);
scope(exit)
{
puts("Freeing ints2!");
free(ints2);
}
}
例外が-betterCモードで実装されていないため、catch、scope(success)、scope(failure)はありません。
選択的ModuleInfo
DRuntimeに依存している一見あいまいな機能にModuleInfo型があります。
これはリフレクションを実現するために背後で働く型で、ほとんどのDプログラマは聞いたこともありません。
ランタイムをなくそうとする人を除いて。
コンパイラがプログラムのモジュールごとにそのインスタンスを生成するため、ModuleInfo型の不足についてリンカエラーがいきなり発生することがあります。
DMD 2.078.0はそれを変更します。
コンパイラはコンパイル時のランタイム実装の存在を認めることで、現在の実装がModuleInfoの宣言を提供しているかどうかを確認できます。
宣言されている場合、インスタンスが適切に生成されます。
宣言されていない場合、インスタンスは生成されません。
これにより、たとえばDでカーネルを書くなどする際にランタイムをなくすことが簡単になります。
その他の変更
WindowsでのDMDの新しいユーザは64ビットの環境をセットアップするのが簡単になりました。
まだMicrosoft build toolsのインストールは必要ですが、DMDは-m64または-m32mscoffをコマンドラインで指定して実行された際に、Microsoft Build ToolsパッケージかVisual Studioがインストールされているか自動検出するようになりました。
以前は自動設定はインストーラのみが行なっており、手動インストールの際は手動で設定する必要がありました。
ユニットテストに対してより粒度の細かいコントロールができるようにDRuntimeが強化されました。
注目すべき点はDの実行ファイルに渡せる--DRT-testmodeフラグです。
現在のデフォルト引数"run-main"では、存在するユニットテストが全て実行され、それが通った後にmainが実行されます。
DMD 2.080.0からデフォルトになる予定の"test-or-main"が渡された場合、ユニットテストが存在すればそれが実行されて、プログラムはその結果のサマリーを出して終了します。
ユニットテストが存在しないならmainが実行されます。
"test-only"の場合mainは実行されず、テストが存在すればその結果が要約されます。
2018年に向けて
これは2018年最初のDMDのリリースです。 今後12ヶ月間Dプログラミング言語コミュニティで何が起こるか非常に楽しみです。 D言語財団の皆様、明けましておめでとうございます!