D言語の更新まとめ 2024年2月版（dmd 2.107.0）

# はじめに

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

今回は前回からほぼ2ヶ月でのリリースで、コンパイラ周りの強化改善、特に内部構造の見直しや安全対策が目玉になっているようです。
今回も一通りの内容ご紹介していきます。

より細かい内容は下記リンクからChangeLogをご覧ください。

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

また、前回である2月版のリリースまとめは以下になります。

- D言語の更新まとめ 2023年12月版（dmd 2.106.0）
    - https://qiita.com/lempiji/items/e4e398bd639dd3c713c5

# 変更点目次

## コンパイラ変更点
- アサート条件としての文字列リテラルは非推奨となりました。
- コンパイラの Makefile が整理されました。
- 識別できないプラグマはエラーではなく、単に無視されるようになりました。
- モジュールコンストラクターに `@standalone` が追加されました。
- `_d_newarray{mTX,miTX,OpT}` が単一のテンプレート ` _d_newarraymTX` に変換されました。

### 改善点
* Bugzilla 14387: アサーション条件として文字列リテラルを許可しないように変更しました
* Bugzilla 23629: importC: コードカバレッジ解析をサポートする必要があります
* Bugzilla 24069: ImportCは、名前のないパラメータとして関数ポインタを解析しません
* Bugzilla 24125: ImportC: ベクトル型の初期化子が理解されません
* Bugzilla 24155: ImportC: C23のデフォルト初期化子を受け入れます
* Bugzilla 24206: TypeSuffixを持つ型を返す関数型のエイリアスができません
* Bugzilla 24238: "lvalueではない"エラーメッセージが混乱を招く問題を改善しました
* Bugzilla 24247: $modifierオブジェクトを使用したコンストラクターが呼び出せないエラーを改善しました
* Bugzilla 24294: ImportC: gccで認識されないコマンドラインオプション -Wno-builtin-macro-redefined
* Bugzilla 24297: ImportCがglibc _FORTIFY_SOURCEと互換性がありません

## ランタイム変更点

### 変更点
- `core.atomic` 操作で無効な `MemoryOrder` を使用することは、コンパイル時に拒否されるようになりました。
- `druntime` の `Makefiles` が整理されました。
- 新しくCの `stdatomic` ヘッダーが追加されました。

### 改善点
* Bugzilla 20332: 連想配列の `clear` 関数は `@safe` であるべきです


## ライブラリ変更点

### 変更点
- `isForwardRange` はオプションの要素型を受け取れるようになりました。
- `Makefiles` が整理されました。

### 改善点
* Bugzilla 11111: `std.algorithm.canFind` が `Needles...` をサポートするようになりました
* Bugzilla 24075: `ushort` または `ubyte` とともに `toChars` を使用できない


## dlang.org 変更点
### 改善点
* Bugzilla 24176: `opApply` デリゲートのパラメータは `ref` である必要がなくなりました
* Bugzilla 24177: 配列リテラルは期待される型に暗黙的に変換される可能性があります
* Bugzilla 24210: 関数の型がドキュメント化されていません

# 注目トピック

## 言語・コンパイラ変更点

### 識別できないプラグマはエラーではなく、単に無視されるようになりました。
あまり関係ない方も多いかもしれない変更です。

以前は、コンパイラが認識できないプラグマ(`pragma(hoge, ...)`など)が使用された場合、エラーが発生していました。このエラーは、`-ignore`オプションを使用しないと回避できませんでした。しかし、今回の変更により、認識できないプラグマは常に無視されるようになります。また、合わせて `-ignore` オプションも無視されるようになりました。

この変更によって何が変わるかというと、開発者が様々なコンパイラの間で異なるプラグマが提供されていても同一ソースが利用できることにつながります。つまりソースの移植性が高まり、コンパイラによる条件分岐を書かなくて良くなるということです。
もう少し具体的に言えば、速い実行速度を求めてLDC向けにチューニングして書いたけどCIのテストやデバッグはDMDのほうが速いしpragma書き分けるか、みたいなケースで対処が不要になり考えることが減る、というのがおおよそのメリットかと考えられます。地味ですが生産性向上策ですね。

### モジュールコンストラクターに `@standalone` が追加されました。

今回の特徴的な強化の1つです。
モジュールコンストラクターに `@standalone` 属性が新たに追加されました。

この機能の目的は、2つのモジュールが相互にインポートされ、それぞれがモジュールコンストラクターを持つ場合、どちらのコンストラクターを先に実行すべきかランタイムが決定できずにエラーを発生させる状況を解決することです。
従来、`pragma(crt_constructor)`を使用してこの問題を回避する方法がありますが、Cランタイムのコンストラクターを使う場合はDのランタイムがまだ初期化されていないため、ガベージコレクターをはじめとするいくつかの機能を利用できません。

そこで、`@standalone` 属性は、Dのランタイムが初期化された後に実行されるが、他のモジュールコンストラクターの実行を前提としないため、循環依存のエラーを回避できるモジュールコンストラクターを修飾するために使われます。これにより、開発者はプロジェクトのモジュール構造を維持しつつ、循環依存に関する問題を解決することが可能になります。

__サンプル__
```d
import core.attribute : standalone;

immutable int* x;

@standalone @system shared static this()
{
    x = new int(10);
}

void main()
{
    assert(*x == 10);
}
```

この属性を使用する場合は、モジュールコンストラクターが他の変数の初期化に依存していないことを手動で保証する必要があり、そのため`@system` または `@trusted` のいずれかでマークする必要があります。可能な限り、`@standalone`を使用する代わりに、問題のあるモジュールコンストラクターをより小さなモジュールに分割し、循環依存の問題を解決することが推奨されます。

`core.attribute` にて提供されるため、`import` して使うことがやや特徴的です。類似の例として `@mustuse` という属性もありますが、新しく追加する属性が何かを壊すことが無いように、という配慮からこのような仕組みになっていると思われます。

プログラムのソースをほとんど変えることなく解決できるという点で、非常にプログラマーに配慮した変更かと思います。できればモジュールコンストラクタを整理しましょうということですが、こういった手段も同時に提供されるところがD言語らしさでしょうか。


### `_d_newarray{mTX,miTX,OpT}` が単一のテンプレート ` _d_newarraymTX` に変換されました。

コンパイラ（ランタイム？）内部実装が変更されました。

以前は、配列の初期化を行う際に`_d_newarray{mTX,miTX,OpT}`という3つのテンプレート関数が使われていましたが、このリリースでは`_d_newarraymTX`という単一のテンプレート関数に統合されました。この新しいテンプレート関数`_d_newarraymTX`は、配列の要素の型に必要な初期化子の種類をDBI(Design by Introspection、内省による設計)を使用してチェックします。この統合によって、`_d_newarraymTX`と`_d_newarraymiTX`の冗長性がなくなり、`_d_newarrayOpT`という汎用実装も不要になりました。

具体的には、以下のようなコード変換が行われます。
```d
S[][] s = new S[][](2, 3)

// 内部的には以下のように変換される:
S[] s = _d_newarraymTX!(S[][], S)([2, 3]);
```

なお、この変更は`core.internal.array.construction` に新しいテンプレートを追加することによって実現されています。

その他、このようなテンプレートの集約を可能にするDesign by Introspectionが気になった方は検索するか、提唱者であるAndrei Alexandrescu氏の[DConf2017講演動画](https://www.youtube.com/watch?v=k31wZafAMhk)を参照してみてください。

このような内部実装の改善は、D言語の将来のバージョンでさらに発展していく可能性がありますので、最新の情報に注意しながらパフォーマンス改善の恩恵を受けていきたいですね。

## ランタイムの変更点


### 新しくCの `stdatomic` ヘッダーが追加されました。

C言語からD言語への移植支援を目的として、Cの `stdatomic` ヘッダーが追加されました。

`stdatomic` ヘッダーは、C11標準で導入された、アトミック操作を行うための機能を提供するヘッダーファイルです。これにより、複数のスレッドが同時にデータにアクセスする際、データの整合性を保つためのいくつかの低レベル操作が可能になります。これはマルチスレッドプログラミングにおいて非常に重要な機能であり、特に並行処理やリアルタイムシステムの開発においては欠かせないものです。
D言語であれば既に `core.atomic` がありますが、それと似たような機能を提供するものだと思っていただければ良いです。

`stdatomic` を追加した主な目的は、CからDへコードを移植する際の労力を減らし、可能な限りCコンパイラと同様のコード生成を実現することです。これにより、Cで書かれた既存のライブラリやアプリケーションをD言語へ移植するプロセスが容易になるので、D言語とC言語の相互運用性が高まります。C言語やC++との連携はD言語の強みでもあるため、今後もこのようなヘッダー拡充は行われていくと思われます。

なお、`stdatomic` モジュールを使用する際、コード生成の品質に特に注意が必要ない場合は、関数名に対応するエイリアスがない場合に `_impl` を追加することで実装に直接アクセスすることができます。
生成されるコードの品質がシステムCコンパイラのものと比較して不十分であったり、使用上の問題となっている場合は、バグとして報告することが推奨されています。


## ライブラリ変更点

### `isForwardRange` はオプションの要素型を受け取れるようになりました。

`isForwardRange` 関数に新しく2番目のテンプレート引数が追加されました。
これは、範囲の要素型を指定することを可能にします。指定された型と範囲の要素型が一致する場合は`true`を返します。この機能により、プログラマーはより精密な型制約をコードに適用でき、意図しない型のデータが使用されることを防ぐことができます。

```d:イメージ
// 例：
static assert(isForwardRange!(int[], const(int)));  // true
static assert(!isForwardRange!(int[], string));     // false
```

この変更によって、コンパイル時により精密な型チェックを行うことができるようになります。これは、プログラムの安全性を高め、意図しない型のデータが範囲内で使用されるのを防ぐのに役立ちます。
例えば、数値の配列に対して文字列を操作しようとする場合、この新しい `isForwardRange` のチェックを用いると簡単に `static assert` を使った検証が書けるため、謎の型不一致のエラーメッセージを回避したりできます。

ちなみに[前回 2.106.0](https://qiita.com/lempiji/items/e4e398bd639dd3c713c5?utm_campaign=post_article&utm_medium=twitter&utm_source=twitter_share#isinputrange-%E3%81%AF%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%A6%81%E7%B4%A0%E5%9E%8B%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F) でも同じような強化が `isInputRange` に対しても行われました。似たようなテンプレートなので同様の措置が入ったと考えれば良いかと思います。


### `std.algorithm.canFind` が `Needles...` をサポートするようになりました

D言語の標準ライブラリにある`std.algorithm`モジュールの`canFind`関数が強化されました。
この更新では、`canFind` 関数で複数の検索キー（`Needles...`）のサポートが追加されています。これは、要するに「検索する値」として何を受け入れるか、という制約が緩まった、ということです。配列やレンジに対して、一つまたは複数の要素を簡単に検索できるようになります。

従来、`canFind`関数は一つの要素またはレンジを検索する機能を提供していましたが、この強化により、複数種類の検索値（`Needles`）を同時に検索することが可能になります。
内部的には、`find(haystack, needles)` 関数を呼び出し、その中で `startsWith` 関数を使用します。この`startsWith` 関数は、要素と範囲の両方を受け入れられるため、表面的な制約を外すだけで柔軟な検索が行えるようになります。

言葉で表現するのも難しいので、更新前と更新後のユニットテスト例を通じて、具体的な使用方法を示します。

__更新前のユニットテスト__
```d
const arr = [0, 1, 2, 3];
assert(canFind(arr, 2));
assert(!canFind(arr, 4));

assert(canFind(arr, [1, 2], [2, 3]));
assert(canFind(arr, [1, 2], [2, 3]) == 1); // 1番目の検索キーが最初に見つかった
assert(canFind(arr, [1, 7], [2, 3]));
```

__更新後のユニットテスト__
```d
const arr = [0, 1, 2, 3];
// 中略

// 複数の検索キーで1つを検索
assert(arr.canFind(2, 3) == 1);
assert(arr.canFind(3, 2) == 2); // 2番目の検索キーが最初に見つかった
assert(arr.canFind([1, 3], 2) == 2); // 配列と要素が混在した検索もできるようになった
assert(arr.canFind([0, 1], 2) == 1); // 配列を指定するとちょっとわかりづらい？
assert(arr.canFind([1, 2], 2) == 1); // 1,2が先に見つかる
assert(arr.canFind([2, 3], 2) == 2); // 単独の2が先に見つかる
```


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

今回は、非推奨とエラーが1件ずつ、合計2件です。

また今後の予定などは以下のページにまとまっていますので、こちらも参考としてみてください。

https://dlang.org/deprecate.html

__非推奨__
- アサート条件としての文字列リテラルは非推奨となりました。

__削除/エラー__
- `core.atomic` 操作で無効な `MemoryOrder` を使用することは、コンパイル時に拒否されるようになりました。

### 非推奨 : アサート条件としての文字列リテラルは非推奨となりました。

文字列リテラルをアサート条件に使用することが非推奨とされました。これは、開発者が予期せず、不適切な使い方をしてしまう可能性があるためです。

たとえば次のようなコードがあります。

```d
assert("unexpected runtime condition");
static assert("unhandled case for `", T, "`");
```

これらの `assert` は、開発者の期待に反して、常に効果を発揮しないままになっています。`assert` の第1引数は条件値、つまり、文字列なのですが、それが `null` ではないので常に `true` と評価されています。結果、この `assert` が失敗することはない状況です。

元々意図していたところとしては、恐らく `assert(0, "message")` と書くべきところで `0` や `false` を省略してしまい、意図せず `assert("message")` と書いてしまうことにあります。

というわけで、このリリースからこのような使用法は非推奨とされ、開発者に警告が表示されるようになりました。

```
source\app.d(5,9): Deprecation: assert condition cannot be a string literal
source\app.d(5,9):        If intentional, use `"unexpected runtime condition" !is null` instead to preserve behaviour
```

もし、文字列リテラルをアサート条件として意図的に使用したい場合は、次のように書くことで回避できます。

```d
assert("" !is null);
static assert("" !is null);
```

この変更により、`assert` の使用方法がより明確になり、意図しない使い方によるバグを減らすことができます。
影響する方は多いかもしれませんが、しっかりと `assert` を書くことで安全なプログラミングができますので頑張りどころですね。


### 削除/エラー : `core.atomic` 操作で無効な `MemoryOrder` を使用することは、コンパイル時に拒否されるようになりました。

今回のリリースにおける比較的重要な変更点です。
`core.atomic`モジュールのいくつかの関数で、無効な`MemoryOrder`を使用しようとするとコンパイル時にエラーが発生するようになりました。
信頼性や安全性の改善施策と言えるかと思いますが、対象となる関数とパターンが色々あるので細かく整理していきます。

#### `atomicLoad` と `atomicStore`

これらの関数は、`MemoryOrder.acq_rel` 引数とともに使用されると、コンパイル時にエラーとなります。これまでは、`atomicLoad` は`MemoryOrder.rel` を、`atomicStore` は `MemoryOrder.acq` を拒否していましたが、これからは両関数が `MemoryOrder.acq_rel` も拒否するようになります。

```d
// エラー例:
atomicLoad!(MemoryOrder.acq_rel)(src);
atomicStore!(MemoryOrder.acq_rel)(dest, value);

// 修正方法:
atomicLoad!(MemoryOrder.seq)(src);
atomicStore!(MemoryOrder.seq)(dest, value);

// または:
atomicLoad(src);
atomicStore(dest, value);
```

__エラーメッセージ__
```
C:\D\dmd2\windows\bin64\..\..\src\druntime\import\core\internal\atomic.d(57,9): Error: static assert:  "invalid MemoryOrder for atomicLoad()"
(中略)
source\app.d(10,34):        instantiated from here: `atomicLoad!(MemoryOrder.acq_rel, shared(int)*)`
```

#### `atomicExchange`

`atomicExchange`関数は、`MemoryOrder.acq` 引数とともに使用されるとエラーになります。

```d
// エラー例:
atomicExchange!(MemoryOrder.acq)(dest, value);

// 修正方法:
atomicExchange!(MemoryOrder.seq)(dest, value);

// または:
atomicExchange(dest, value);
```

__エラーメッセージ__
```
C:\D\dmd2\windows\bin64\..\..\src\druntime\import\core\internal\atomic.d(287,9): Error: static assert:  "Invalid MemoryOrder for atomicExchange()"
(中略)
source\app.d(13,34):        instantiated from here: `atomicExchange!(MemoryOrder.acq, int, int)`
```

#### `atomicCompareExchangeWeak` と `atomicCompareExchangeStrong`

この2つはDMDなどのコンパイラ内部で使われているものなので、あまり目にすることはないかもしれません。（`core.internal.atomic` で定義されていて、`core.atomic`の内部実装で使われる）

これらの関数は、2番目の `fail` 引数が `MemoryOrder.rel` または `MemoryOrder.acq_rel` である場合にエラーを発生させます。また、`fail` 引数が第一の `succ` 引数より大きい値であるときもエラーとなります。この場合 、`MemoryOrder.raw` を使用することが推奨されます。

```d
// エラー例:
atomicCompareExchangeWeak!(MemoryOrder.rel, MemoryOrder.rel)(dest, compare, value);
atomicCompareExchangeStrong!(MemoryOrder.acq, MemoryOrder.rel)(dest, compare, value);

// 修正方法:
atomicCompareExchangeWeak!(MemoryOrder.rel, MemoryOrder.raw)(dest, compare, value);
atomicCompareExchangeStrong!(MemoryOrder.acq, MemoryOrder.raw)(dest, compare, value);
```

__エラーメッセージ__
```
C:\D\dmd2\windows\bin64\..\..\src\druntime\import\core\internal\atomic.d(326,9): Error: static assert:  "Invalid fail MemoryOrder for atomicCompareExchangeStrong()"
source\app.d(16,62):        instantiated from here: `atomicCompareExchangeStrong!(MemoryOrder.rel, MemoryOrder.rel, shared(int))`
```
```
C:\D\dmd2\windows\bin64\..\..\src\druntime\import\core\internal\atomic.d(326,9): Error: static assert:  "Invalid fail MemoryOrder for atomicCompareExchangeStrong()"
source\app.d(17,64):        instantiated from here: `atomicCompareExchangeStrong!(MemoryOrder.acq, MemoryOrder.rel, shared(int))`
```

メモリオーダリングに手を出す方ならご存知の範囲なのではと思わなくもないですが、このような安全のための変更もありますので引き続き非推奨などの情報は追いかけていきたいと思います。

# まとめ

しばらく更新記事を書いておらず2日連続で書きました。今日もAIさんに助けられています。

今回は紹介していませんが、Makefileの更新など、内部の改善に集中されていたリリースだったようです。
一報でライブラリの改善なども多く取り入れられていますので、ぜひとも最新リリースをお試しいただければと思います。
`assert` とか間違ってると地味に怖いですしね。

というわけで今回の翻訳記事は以上です。
D言語便利に使いましょう！