Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What are the problem?

D言語の更新まとめ 2021年10月版(dmd 2.098.0)

はじめに

D言語の公式コンパイラである dmd が更新され、最新バージョンである 2.098.0 が 2021/10/10 にリリースされました。

今回は前回から4ヶ月ほど経ってのリリースとなり、以前は2ヶ月に1回のペースだったため、1回スキップした格好です。
裏では今年もDConf Online 2021の計画があったりで忙しかった様子ですが、今回もその期間に見合うだけの非常に重要な機能強化が複数ありますのでトピックご紹介していきます。

また、あまりコンパイラを更新されない方も、今回は節目の1つになるバージョンかと思いますので、強化内容など把握したうえでぜひバージョンアップを検討いただきたいと思います。

より細かい内容は下記リンクからChangeLogをご覧いただけます。

また、前回6月のリリースまとめは以下になります。

変更点目次

コンパイラ変更点

  • エイリアス代入(Alias Assignment)が追加されました
  • ImportC コンパイラを使って D から C の宣言にアクセスできるようになりました
  • (args) => {} という構文を使用すると、非推奨メッセージが表示されるようになりました
  • C++ 向けヘッダー生成が改善されました
  • -preview=dtorfields がデフォルトで有効になりました
  • ベクトル型に .min.max などのプロパティが追加されました
  • 可変変数を switch case として使用するとエラーが発生するようになりました
  • 境界外の配列にアクセスした場合、より適切なエラーメッセージが表示されるようになりました
  • クラスアローケーターが言語から削除されました
  • immutable なグローバルデータを static this で初期化すると、エラーが発生するようになりました
  • オペレーティングシステム、C、およびC++ランタイムのクロスコンパイルのために -target=<triple> が追加されました
  • 最初のメンバーではない union フィールドのデフォルトの初期化でエラーが発生するようになりました

改善点

  • Bugzilla 15889: 配列の境界アクセスチェックは配列の長さとインデックスを報告すべきです
  • Bugzilla 16001: ラムダ構文: FunctionLiteralBody : (x) => {assert(x);} のような使用を禁止します
  • Bugzilla 16689: インスタンス化された mixin template のエラーは、インスタンス化ポイントを表示すべきです
  • Bugzilla 17400: エラーメッセージの "candidates are:" の前に改行を入れます
  • Bugzilla 18907: クロスコンパイルのサポート
  • Bugzilla 21885: 不適切な診断: @disable でアノテーションされると構造体がコピー不可となる
  • Bugzilla 21997: CTFEは、異なる属性を持つ関数ポインタのキャストを許可すべきです
  • Bugzilla 22038: final switch のエラーメッセージはすべての見つからない enum のメンバーを報告すべきです
  • Bugzilla 22115: if (s.a == 3 ? s : null) を if (s.a == 3) に最適化します
  • Bugzilla 22138: foreach はループ変数をスコープとして宣言することができません
  • Bugzilla 22227: if (scope f = x()) と while (scope f = x()) が解析されない

ランタイム変更点

  • 集成体の TypeInfo 名が完全修飾され、一意になるようになりました
  • Posixシステム用のコンカレントGCが追加されました
  • POSIXのインポートが改善されました

改善点

  • Bugzilla 22169: core.sys.posix.stringmemccpy, stpcpy, stpncpy, strnlenpure にします

ライブラリ変更点

  • std.utfisValidCharacter 関数が追加されました

改善点

  • Bugzilla 16210: std.utf.byUTF を双方向レンジに対応させます
  • Bugzilla 16218: Windows の std.file.readImpl@system と宣言されるべきです
  • Bugzilla 18632: fromStringzchar[n] で使用できるようにします。
  • Bugzilla 20665: std.concurrency.spawndelegate で動作しないと文書化すべきです
  • Bugzilla 21926: std.concurrency.octal で先行するゼロを許可します
  • Bugzilla 22100: Nullable の連鎖割り当てをサポートします
  • Bugzilla 22225: SumType: 一部の代入は @safe なコードで実行できるようにする必要があります

DUB 変更点

  • dub の settings ファイルと dub.json/dub.sdl で、コンパイルと実行およびテストオプションで使用する環境変数がサポートされました

dlang.org 変更点

改善点

  • Bugzilla 21495: File.readf のドキュメントには、何が返されるのかが記載されていません
  • Bugzilla 21600: Regex.namedCaptures が文書化されていません

注目トピック

言語・コンパイラ変更点

追加 : エイリアス代入(Alias Assignment)が追加されました

テンプレート内のエイリアス宣言に新しい値が割り当てられる再代入の機能が追加されました。
例えば、以下のような再帰的なテンプレートを、より手続き的に書くことができるようになったりします。

従来の再帰を使った例

template staticMap(alias F, T...)
{
    static if (T.length == 0)
        alias staticMap = AliasSym!();
    else
        alias staticMap = AliasSym!(F!(T[0]), staticMap!(T[0 .. T.length]));
}

alias代入を使った例

template staticMap(alias F, T...)
{
    alias A = AliasSeq!();
    static foreach (t; T)
        A = AliasSeq!(A, F!t); // ここで alias 代入
    alias staticMap = A;
}

alias で宣言したものがあたかも変数のように再代入されているのが分かると思います。これが「alias 代入」と呼ばれる機能です。

この機能が導入された目的としては、再帰によるテンプレートの特殊化が組み合わせ爆発することを防ぐ意図があります。
組み合わせ爆発が起きると当然コンパイル時間が伸びるといった悪影響があるので、こういった機能で特殊化を減らし、コンパイルの高速化が期待できます。コンパイル速度は大切です。

コンパイル時のメタプログラミングが強力である点はD言語の強みの1つでもあるので、今後もこういった機能で伸びることを期待しつつ、1ユーザーとしても活用していきたいですね。

強化 : ImportC コンパイラを使って D から C の宣言にアクセスできるようになりました

今回の目玉となる強化項目で、 D言語のコンパイラでC言語のソースがコンパイルできる ようになりました!

え?どういうこと!?

D言語はC言語やC++との連携を強く意識した言語であるため、その相互運用を改善するために様々な取り組みが行われてきました。
たとえばC言語のヘッダーをDっぽく変換する htoddstep といったツール、clnagを中で使ってC++の定義をそのまま取り込んで使えるようにする dpp といったプロジェクトがあります。

そして今回もその系列、特に抜本的なC言語サポートの1つとして、C言語の プリプロセス済み ソースを直接解析してコンパイルできる機能がD言語のコンパイラに組み込まれた、という強化です。
これによって、Cの分を別々にコンパイルする手間、リンクする手間、ヘッダー変換する手間、自身の言語に書き換える手間、を総じてほぼゼロにできる可能性が見えてきます。

論より証拠ということで、ざっとCのソースをコマンド実行する例です。

Cのソース

int printf(const char*, ...); // 通常なら #include <stdio.h> と書く

int main(void)
{
    printf("Hello, world!");
}

実行

dmd test.c
./test

実行結果

Hello, world!

動いてる。すごい。

機能概要

このC言語をコンパイルする機能は「ImportC コンパイラ」と呼ばれます。文字通り「Cを取り込むコンパイラ」が内臓された恰好なので、そのままですね。

ここでサポートされる言語仕様としては ISO/IEC 9899:2011 (C11) をベースに、D言語と辻褄が合うような方言になっているとのことです。

約4ヶ月とかなりの短期間で実装されたこともあり様々な制約があるのですが、そこは過去に世界初のネイティブC++コンパイラまで書いたことがあるWalter Bright氏、その経験をもとに多く機能はなんとかしているようです。

まだ対応されていない例や制限としては volatileregister キーワードが無視される、 const がDと同様の推移性を持つ、などがあります。
とはいえ無視されるだけなら普通に使えるものもあり、結構なソースが通用するみたいです。

これらについても仕様のページが作られているので、詳細はこちらからご確認ください。

なお、C言語とD言語のソースを混ぜてコンパイルすることもでき、すべて既存D言語と同じ内部表現を持った状態になるので、これを使って C言語がCTFE(コンパイル時関数実行)できる というのは非常に面白い点かと思います。あとは、わかりますね…?

今後はCで書かれた数多の資産をD言語から直接使える道ができたということで、言語的にも圧倒的な価値向上につながっているのではないでしょうか?

もしお手元に何かC言語で書かれたライブラリがあれば、ぜひ試してみてください!

補足 : プリプロセス済みソースの入手方法

ImportCコンパイラでは「プリプロセス済みソースのみ」受け付けることになっています。

これはプリプロセスの処理が非常に厄介だからなのですが、#define#include を含まないソースなど世の中にほとんどありませんよね。

そこで、GCC や clang など一般的に入手できるC++コンパイラには、コンパイラスイッチでプリプロセス済みソースを出力する機能がついているのでご紹介しておきます。
(ImportCのページにも同様の記載があります)

GCC と clang では同じ -E というスイッチが使えるので、こちらを使ってみてください。

gcc -E file.c > file.i
clang -E file.c > file.i

補足 : 他プロジェクトとの関係

今回ImportCコンパイラが組み込まれたと言っても、関連プロジェクトである dstepdpp の価値がなくなったわけではありません。

dstep は Objective-C の宣言に対応しているので、こちらと連携する場合は引き続き有用です。

dpp は前述の通り C++ の宣言と定義をclangでコンパイルしてまとめてリンクする仕組みなので、C++連携の上では引き続き有用です。(ImportC++コンパイラに進化する目途は今のところない)

それぞれ目的に合わせて使い分けていきましょう。

変更 : -preview=dtorfields がデフォルトで有効になりました

クラスのデストラクタを明示的に実装している場合に影響のある -preview=dtorfields が既定で有効になりました。
このフラグは 2.075 で実装されており、2017年6月からなんと4年越しで有効になりました。

具体的な該当コードは下記のようになりますが、デストラクタの属性違反が一部見逃されていた問題に対し、今後はエラーとして扱われるようになります。

struct Array
{
    int[] _payload;
    ~this()
    {
        import core.stdc.stdlib : free;
        free(_payload.ptr);
    }
}

class Scanner
{
    Array arr;
    this() @safe {} // Arrayであるarrのデストラクタを呼ぶが、Arrayのdtorは@safeではない
}

見つけた場合はデストラクタの属性を確認して適切に修正しましょう。

追加 : ベクトル型に .min.max などのプロパティが追加されました

組み込みのベクトル型(float4 など)に .min.max 等のプロパティが追加されました。

整数ベクトル型([u]byte16, [u]short8, [u]int4, [u]long2)には .min.max が追加されました。

浮動小数点数のベクトル型(float4, double2)には .min.max.min_normal.nan.infinity.epsilon が追加されました。

それぞれ要素型の当該プロパティをベクトル要素に持つ値、となっています。

また以下のようにして同様の値を得ることができます。

import core.simd;

static assert(float4.max == cast(float4)float.max);

強化 : オペレーティングシステム、C、およびC++ランタイムのクロスコンパイルのために -target=<triple> が追加されました

ついに dmd までもがクロスコンパイラになりました。

今回の強化により、WindowsでLinux向けのバイナリを吐いたり、LinuxでWindows向けのバイナリを作ったりできます。

元々、LLVMベースだった ldc はクロスコンパイラだったのですが、dmd も同じコンパイラオプションが追加された格好です。

指定可能な値にはかなりの組み合わせがあるので、具体的なところは公式ドキュメントなど参考としていただければと思います。

改善 : foreach のループ変数が scope として宣言できるようになりました

見出しの通りの変更です。以下のようなコードが書けるようになりました。

int[] arr;
foreach (scope a; arr)
{
    // 処理
}

D言語の scope 変数は、その変数が宣言されたスコープから外に値がコピーされたり持ち出されることを防ぐ機能を持っています。

通常ループ変数というのはそのループに閉じた利用を想定するかと思いますので、これは非常に妥当な改善です。

コンパイラにスコープの検証を任せることができるため、健全性という意味では何も書かない foreach の多くに試しに付けてみるべきものと思われます。

余裕のある方はぜひ試してみてください。

改善 : いくつかのエラーメッセージが改善されました

類似の改善があったので、エラーメッセージの改善ということでまとめてご紹介します。

  • 配列のインデックスアクセスのエラーでは、配列の長さとアクセスしようとしたインデックスが含まれるようになります
  • final switchenum のメンバーが不足している場合は、その不足しているメンバーが何か含まれるようになります
  • mixin template のインスタンス化でエラーが発生した場合、インスタンス化された場所の情報が含まれるようになります

エラーメッセージの改善はデバッグ効率に直結するのでありがたいですね。

こういうので良いんだよ感があり、ぜひ有効活用していきたいと思います。

ランタイム変更点

変更 : 集成体の TypeInfoname が完全修飾され、一意になるようになりました

TypeInfo というのは実行時型情報を表現するための型です。たとえば typeid(obj) といった構文でオブジェクトの型に合わせて取り出すことができます。

このトピックは TypeInfo が持つ name プロパティの話で、以前テンプレート引数を考慮した完全修飾となっていなかったところ、現在は完全修飾されるようになった、というものです。結果として、より長い名前になる場合があるので、互換性の意味では要確認ポイントとなります。

また TypeInfo_Struct のインスタンスは、かなり短くなる可能性のある mangledName というプロパティのみを実際に保存しており、最初の name または toString() の呼び出し時に、mangledName のマングリングを解除して使うようになりました。(これはスレッド毎にキャッシュされます)

したがって、TypeInfo の構造体ごとに一意の文字列が必要となる場合は、計算を伴う name よりも mangledName を使用すると効率的です。name はデマングルの処理とキャッシュの副作用を挟むため、@nogcpure でないというデメリットもあります。

強化 : Posixシステム用のコンカレントGCが追加されました

GC関連の強化です。

システムコールとして fork (Linuxでは clone)をサポートしている Posix 環境にて、GCをプロセス並列で行うための Fork GC と呼ばれる実装が追加されました。

通常GCというと Stop the world と呼ばれる通り、GCのためにすべての計算を止めてメモリ空間を走査、直接利用されていないメモリをマーキングして開放する、という動きをすることが多いです。(止めずにマーキングする手法もあり、一概には言えませんが)

今回 fork を活用することで、プロセスの持つメモリを複製したような状態のプロセスを構築、そちらで並列にマーキング処理をして、実際の開放処理だけ元のプロセスでやる、という「マーキング処理の完全並列化」が可能になりました。

※ 一度参照されなくなったメモリが再度参照され始めることはないので、複製したメモリ情報でも破棄されたメモリは誤検知なく検出できます。逆に、新しく破棄できるようになったものが残る可能性はあります。

使用するには実行時のGCオプション等を使い以下のようにします。

dub run -- --DRT-gcopt=fork:1

コード中に含める場合、ソースのどこかに以下のようなグローバル変数を宣言します。

extern(C) __gshared string[] rt_options = [ "gcopt=fork:1" ];

GCに関連する他のオプションについては以下も参照してください。かなり増えてきた感じです。

改善 : POSIXのインポートが改善されました

2つのシンボルが core 以下の定義に追加されました。定義通りですので、特にコメントはありません…

  • lwpid_tcore.sys.linux.sys.procfs に追加されました。
  • O_CLOEXECcore.sys.posix.fcntl に追加されました。

ライブラリ変更点

強化 : std.utfisValidCharacter 関数が追加されました

関数名の通り、不正な文字(文字列ではなく文字)を検知する関数です。何かニッチな用途があったのかもしれません。

assert(!isValidCharacter(cast(char) 0x80));
assert(isValidCharacter('ä'));

ちなみに文字列としてユーザー入力の検証がしたい場合は std.utf.validate 関数が使えます。

DUB変更点

強化 : dub の settings ファイルと dub.json/dub.sdl で、コンパイルと実行およびテストオプションで使用する環境変数がサポートされました

コンパイルや実行の際に使われる環境変数を追加指定できる機能です。地味ですが、結構重要な変更です。

環境変数の定義が dub.jsondub.sdl といった設定ファイルでできるので、 configuration 毎に環境変数を切り替える等ができます。
version よりも少し緩い動的なスイッチみたいに使えますね。

たとえば VAR という名前の環境変数に Foo を設定する場合は以下のように記載します。

dub.json

{
    "environments": {
        "VAR": "Foo"
    }
}

dub.sdl

environments "VAR" "Foo"

これは常に有効な環境変数ですが、ビルド前に設定する、ビルド後に設定するといった細かい制御ができ、以下の種類があります。
外部のコマンド呼び出しにも効くので、ぜひ有効活用していきたいですね。

  • environments
  • buildEnvironments
  • runEnvironments
  • preGenerateEnvironments
  • postGenerateEnvironments
  • preBuildEnvironments
  • postBuildEnvironments
  • preRunEnvironments
  • postRunEnvironments.

非推奨または廃止される機能

今回新たに非推奨となる機能は1点、また以前から非推奨だった機能が4つ廃止されます。
リリースまで4ヶ月空いた割には前回より少なく、影響の大きいトピックは特にないので直接問題に遭遇することは稀かと思われます。

個々の対応方法についてもまとめていきます。

非推奨

  • (args) => {} という構文を使用すると、非推奨メッセージが表示されるようになりました

削除/エラー

  • 可変変数を switch case として使用するとエラーが発生するようになりました
  • クラスアローケーターが言語から削除されました
  • immutable なグローバルデータを static this で初期化すると、エラーが発生するようになりました
  • 最初のメンバーではない union フィールドのデフォルトの初期化でエラーが発生するようになりました

非推奨 : (args) => {} という構文を使用すると、非推奨メッセージが表示されるようになりました

いわゆる「ラムダ式」と呼ばれるデリゲートを構築するための構文に関する変更です。
以下のような記述が対象となります。

(args) => {}

JavaScriptやC#を使う方には非常に見慣れた構文かと思います。

このように書いてあれば、通常「本文が空で何もしないデリゲートを作るラムダ式」ですよね。

ところがこちら、D言語では「空のデリゲートを返すラムダ式」となります。式としては {} だけで「引数なしデリゲートを作る式」なので、他の言語に慣れた新規参入者の直感に反している、というのが非推奨の主な理由です。

ちなみに他の言語と同じ動きとなる「本文が空で何もしないデリゲートを作る式」は (args) {} と書きます。
これは引き続き警告なく使えますので、もし警告が意図しないものであればこちらに書き換えてください。

本当に「デリゲートを返すデリゲート」が作りたい場合、 (args) => () {} と書きます。これは引数を受け取る括弧が付くことで「デリゲートと分かっている」と判断され警告が出なくなります。

これは以下のような警告にも表示されますので、見たらそのように直せばOKです。

app.d(5): Deprecation: Using `(args) => { ... }` to create a delegate that returns a 
delegate is error-prone.
app.d(5):        Use `(args) { ... }` for a multi-statement function literal or use `(args) => () { }` if you intended for the lambda to return a delegate.

※ 個人的には「=> の右側が式と分かれば良い」と思うので、 (args) => ({}) と右辺を括弧で包む書き方が好みではあります。

エラー : 可変変数を switch case として使用するとエラーが発生するようになりました

2.073.2 (2017年3月)の頃から非推奨メッセージが出ていたものがエラーになりました。

switch 文の case 句に可変引数を書くとエラーになる、という内容で以下のようなコードがえらー例です。

void foo(int s, int x, const int y, immutable int z) {
    switch (s) {
        case x: // x は const/immutable でないためエラー
        case y: // y は const であるため許可
        case z: // z は immutable であるため許可
        default:
    }
}

え?そんなところに変数書くの?という気がしますよね?しませんかね?
とにかくこれが書けてしまうのは確かでした。

通常 case ではコンパイルの時点で既知となる値のみ書くことで効率的なジャンプテーブルを使ったコードが生成できます。
そうでなければ、実質 if-else を連打しているのと変わらないコードが生成されます。

以下のようなエラーですので、遭遇したら順当に if-else で書き直すという対応になります。

app.d(11): Error: `case` variables have to be `const` or `immutable`

削除 : クラスアローケーターが言語から削除されました

2.080.0 (2018年5月)から非推奨だったクラスアロケーターが正式に削除されました。

クラスのインスタンスを置くメモリ確保ロジックを自作する、というもので、GCに影響が出たり複雑だったりで非推奨になった経緯があります。

使っている方はもうほとんどいないと思いますが、クラスのインスタンスを malloc したメモリに置くといった置き換えの例は、非推奨がまとめられた以下のページに記載されています。

該当する場合はこちらご確認ください。

エラー : immutable なグローバルデータを static this で初期化すると、エラーが発生するようになりました

2.087.0 (2019年7月)から非推奨だった immutable なグローバル変数を static this で初期化するコードがエラーとなりました。

具体的なコードは以下のようなものです。

module foo;
immutable int bar;
static this()
{
    bar = 42;
}

なぜこれがダメかというと、static this はスレッド毎に実行される、主に TLS(Thread Local Storage) を初期化するためのものだからです。

immutable なグローバル変数は「変化しないグローバル変数」であるため、スレッド毎に確保される TLS にはならないのですが、このコードでは変化しないはずの本当にグローバルなストアを何度も書き換えてしまうことになります。

というわけで、immutable なグローバル変数は、プログラム起動時に一度だけ実行される shared static this で初期化すればOKです。

エラーにもそのように表示されますので、こちらを見たら shared とつける等の対策を取ってください。

app.d(12): Error: immutable variable `bar` initialization is not allowed in `static this`
app.d(12):        Use `shared static this` instead.

エラー : 最初のメンバーではない union フィールドのデフォルトの初期化でエラーが発生するようになりました

2.088.0 (2019年9月)から警告だった union 定義とデフォルト初期化の組み合わせが一部エラーになるようになりました。

エラーが発生するコードの例は以下のようなものです。

union U
{
    int a;
    long b = 4;
}

union は仕様として「最初のフィールドによって初期化される」となっています。
つまり、この例だと long b = 4; は無視され、0 で初期化されるということです。良くは無いですね。

対応としては、デフォルト初期化を持ったフィールドを一番上に持ってくる、という方法になります。
下記の通りエラー文にも 「b4 で初期化されるので、a より前にして」と言われます。こちらを見たら素直に従えば良いかと思います。

app.d(11): Error: union field `b` with default initialization `4` must be before field `a`

まとめ

ついにD言語に ImportC コンパイラが導入され、C言語との本格連携の道が幕を開けました。
今後バージョンを重ねるにつれ、今は動かないライブラリもどんどん使えるようになっていくかと思います。

リリース期間は1回飛ばす格好になりましたが、それを持ってしても十二分な機能強化が多数ある印象です。

次は11月に DConf Online 2021 が予定されていますので、こちらに注力されるかもしれません。

とはいえ強化は引き続きあるかと思いますので、次回リリース予定は12月1日、今後の進歩にも期待したいと思います!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
3
Help us understand the problem. What are the problem?