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

# はじめに

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

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

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

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

- ChangeLog
    - https://dlang.org/changelog/2.098.0.html

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

- D言語の更新まとめ 2021年6月版（dmd 2.097.0）
    - https://qiita.com/lempiji/items/9ac6dcb57b028427ad7a

# 変更点目次

## コンパイラ変更点

- エイリアス代入(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.string` の `memccpy`, `stpcpy`, `stpncpy`, `strnlen` を `pure` にします

## ライブラリ変更点

- `std.utf` に `isValidCharacter` 関数が追加されました

### 改善点

* Bugzilla 16210: `std.utf.byUTF` を双方向レンジに対応させます
* Bugzilla 16218: Windows の `std.file.readImpl` は `@system` と宣言されるべきです
* Bugzilla 18632: `fromStringz` を `char[n]` で使用できるようにします。
* Bugzilla 20665: `std.concurrency.spawn` は `delegate` で動作しないと文書化すべきです
* 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)が追加されました

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

__従来の再帰を使った例__

```d
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代入を使った例__

```d
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っぽく変換する `htod` や `dstep` といったツール、clnagを中で使ってC++の定義をそのまま取り込んで使えるようにする `dpp` といったプロジェクトがあります。

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

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

__Cのソース__

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

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

__実行__

```console
dmd test.c
./test
```

__実行結果__

```console
Hello, world!
```

動いてる。すごい。

#### 機能概要

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

ここでサポートされる言語仕様としては [__ISO/IEC 9899:2011__](https://en.wikipedia.org/wiki/C11_(C_standard_revision)) (C11) をベースに、D言語と辻褄が合うような方言になっているとのことです。

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

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

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

- ImportC
    - https://dlang.org/spec/importc.html

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

```d
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` が追加されました。

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

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

```d
import core.simd;

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


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

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

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

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

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

- https://dlang.org/changelog/2.098.0.html#target


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

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

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

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

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

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

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


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

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

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

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

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



## ランタイム変更点

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

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

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

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

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


### 強化 : 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に関連する他のオプションについては以下も参照してください。かなり増えてきた感じです。

- https://dlang.org/spec/garbage.html#gc_config

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

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

- `lwpid_t` が `core.sys.linux.sys.procfs` に追加されました。
- `O_CLOEXEC` が `core.sys.posix.fcntl` に追加されました。


## ライブラリ変更点

### 強化 : `std.utf` に `isValidCharacter` 関数が追加されました

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

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

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

https://dlang.org/phobos/std_utf.html#validate

## DUB変更点

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

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

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

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

__dub.json__

```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) => {}` という構文を使用すると、非推奨メッセージが表示されるようになりました

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

```javascript
(args) => {}
```

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

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

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

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

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

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

```console
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` 句に可変引数を書くとエラーになる、という内容で以下のようなコードがえらー例です。


```d
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` で書き直すという対応になります。

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


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

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

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

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

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

https://dlang.org/deprecate.html#Class%20allocators%20and%20deallocators

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

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

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

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

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

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

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

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

```console
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` 定義とデフォルト初期化の組み合わせが一部エラーになるようになりました。

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

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

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

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

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


# まとめ

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

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

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

https://dconf.org/2021/online/index.html

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