D言語の更新まとめ 2025年3月版（dmd 2.110.0）

# はじめに

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

今回は最終リリースの 2.109.1 から約9ヶ月空いたリリースで、D言語史上でも遅れに遅れたリリースとなりました。

遅れた背景については色々あるようですが、それも含めて一通りの内容をご紹介していきます。
より細かい内容は下記リンクからChangeLogをご覧ください。

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

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

- D言語の更新まとめ 2024年6月版（dmd 2.109.0）
    - https://qiita.com/lempiji/items/86cbb44ebd051816cae5


# 変更点目次

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

### 変更点

- `-preview=fixImmutableConv` で `const(void)[]` から `void[]` へのコピーが禁止されました。
- インポート式が16進文字列として扱われるようになりました。
- 新しい特性（trait）として `isCOMClass` が追加され、型がCOMクラスかどうかを検出できるようになりました。
- `bool` 型の値が `0` または `1` 以外の場合、`@safe` ではなくなりました。

### 改善点

1. Bugzilla 20243: 連想配列のキー型で `inout` が適用されるようになります。
2. Bugzilla 24135: メンバー関数のオーバーロードが正しく表示されるようになります。
3. Bugzilla 24580: ImportC: 属性の後にアセンブリラベルがあると構文エラーになる問題が修正されました。
4. Bugzilla 24598: OpenBSDでのコンパイラテストが調整されました。
5. Bugzilla 24623: バージョン識別子の `CppRuntime_Clang/Gcc` が `CppRuntime_libcxx/libstdcxx` に変更されました。
6. Bugzilla 24841: `UTF-16` サロゲートが文字列のエスケープとして使用される際にエラーを示すヒントが追加されます。

## ランタイム変更点

### 改善点

- Bugzilla 20859: `core.sync.rwmutex.ReadWriteMutex.Reader/Writer.tryLock` にタイムアウトを取るオーバーロードを追加しました。
- Bugzilla 24590: `_d_criticalenter2` におけるモジュールコンストラクターのサイクルと共有 `libphobos2` における不適正命令の問題が解決されました。

## Dub変更点

### 変更点
- `dub.selections.json` ファイルは親ディレクトリも検索されるようになりました。

# 注目トピック
## 言語・コンパイラ変更点

### `-preview=fixImmutableConv` で `const(void)[]` から `void[]` へのコピーが禁止されました。

データ操作の安全性を高めるため、`-preview=fixImmutableConv` スイッチの動作が強化されました。
これは以下のようなケースで、`const(void)[]` データに tail const ポインタが含まれる場合、`void[]` へのコピーが後から `const` データを破壊する可能性があることへの対処です。

```d
void f(int*[] a, const int*[] b)
{
    void[] va = a;
    const void[] vb = b;
    va[] = vb[]; // b のポインタが a にコピーされる
    *a[0] = 0; // a を通じて const なデータが変更される
}
```

今後 `-preview=fixImmutableConv` スイッチが有効な場合、`const(void)[]` データを `va` にコピーすることは許可されなくなります。

実際に試すと以下のようなエラーが発生します。

```
source\app.d(12,10): Error: cannot copy `const(void)[]` to `void[]`
source\app.d(12,10):        Source data has incompatible type qualifier(s)
source\app.d(12,10):        Use `cast(void[])` to force copy
```


### インポート式が16進文字列として扱われるようになりました

こちらはバイナリの埋め込みを使いやすくするための強化です。

インポート式は文字列として型付けされていますが、バイナリファイルを埋め込むためにも使用されます。
これまでは、16進文字列として扱うためにキャストが必要でしたが、今回の変更により、 `char` 以外の整数型の配列に暗黙的に変換されるようになりました。

```d
// 以前はキャストが必要
immutable ubyte[] iconImg = cast(immutable ubyte[]) import("icon.png");

// 今後は暗黙的にubyte配列に変換される
immutable ubyte[] iconImg = import("icon.png");
```

なお、この機能に出てくる「16進文字列」と呼ばれているものは、dmd 2.108.0 で行われた以下の機能強化に関係するためです。

https://qiita.com/lempiji/items/73e2af3d452ca21466e0#16%E9%80%B2%E6%95%B0%E3%81%AE%E6%96%87%E5%AD%97%E5%88%97%E3%81%8C%E6%95%B4%E6%95%B0%E9%85%8D%E5%88%97%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%95%E3%82%8C%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

`import` を式として扱う時は元々ただの文字列として扱われていましたが、今回から `x"41424344"` のような16進数リテラルとして扱われるようになったということです。


### 新しい特性（trait）として `isCOMClass` が追加され、型がCOMクラスかどうかを検出できるようになりました

Windowsプログラミングにおいて稀に出てくる COM クラスを扱うための機能強化です。
かなり古い印象はあるものの、まだ強化要望があるというのは流石マイクロソフト技術ですね。

今回は、COMクラスであるかどうかを判定するための `__traits(isCOMClass, Type)` が追加されました。
また、`__traits` はコンパイル時に静的に判定する目的で、実行時には `TypeInfo_Class` フラグを使用して判定できます。

サンプルコードとしては以下のようになります。

```d
import std.stdio;

import core.sys.windows.com; // IUnknown, ComObject

class MyCOMobject : ComObject { }

class NotCOMClass { }

void main() {
	// traits による判定
    writeln(__traits(isCOMClass, IUnknown)); // true
    writeln(__traits(isCOMClass, MyCOMobject)); // true
    writeln(__traits(isCOMClass, NotCOMClass)); // false

	// TypeInfo_Class による判定
    auto obj = new MyCOMobject(); // 簡単のため new で生成
    TypeInfo_Class t1 = typeid(obj);
    writeln(t1.m_flags & TypeInfo_Class.ClassFlags.isCOMclass); // 1

    auto obj2 = new NotCOMClass();
    TypeInfo_Class t2 = typeid(obj2);
    writeln(t2.m_flags & TypeInfo_Class.ClassFlags.isCOMclass); // 0
}
```

使い道は色々あると思いますが、個人的にはテンプレート引数のクラスがCOMクラスならエラーにする、といった使い方しか浮かびませんでした。
もし実務でCOMを触る方や、これを使って何か面白いことを思いついた方がいればぜひ共有してください。

## ランタイム変更点

### `core.sync.rwmutex.ReadWriteMutex.Reader/Writer.tryLock` にタイムアウトを取るオーバーロードを追加しました。

`core.sync` モジュールは、スレッド間同期の仕組みを提供するモジュールです。
この中にある `ReadWriteMutex` は、複数スレッドからデータの読み取りと書き込みを行う時に読み取り同士は排他しなくても良いことに着目して、パフォーマンスを上げようというものです。（ほとんどが読み取り、稀に書き込み、というバランスで特に有効）

追加されたメソッドは以下のようなシグネチャのものです。

```d
@trusted bool tryLock(Duration timeout);
shared @trusted bool tryLock(Duration timeout);
```

使ってみると以下のようになります。

```d
import core.sync.rwmutex;
import core.time;

void main()
{
	auto rwm = new ReadWriteMutex();

    // ロックを取る時にタイムアウトを指定
    // trueの時はロックできた状態、falseの時はタイムアウト
	if (rwm.reader.tryLock(1.seconds)) {
		scope (exit) rwm.reader.unlock();
		writeln("Reader locked");
	} else {
		writeln("Reader lock failed");
	}
}
```

## Dub変更点

### `dub.selections.json` ファイルは親ディレクトリも検索されるようになりました。

`dub.selections.json` ファイルは、プロジェクトの依存関係を管理するためのファイルです。

このファイルは通常、プロジェクトのルートディレクトリに配置されますが、今回の変更により、親ディレクトリにも配置できるようになりました。
つまり、複数のプロジェクトを含むリポジトリに対して、共通の `dub.selections.json` ファイルを配置することができるようになったということです。

この共通で使うファイルには `inheritable: true` というフラグを付けておくことで、そのリポジトリ内のすべてのプロジェクトに適用されるようになります。
ただし、各プロジェクトでローカルの `dub.selections.json` ファイルがある場合は、そのファイルが優先されます。
また、 `inheritable: true` が付いているファイルは、ネストされたプロジェクトで `dub upgrade` などのコマンドを実行しても更新されません。

この機能によって、サブパッケージをたくさん持つプロジェクトの場合、一部のパッケージだけ更新してしまって壊れるような事態が防げます。

大きめのライブラリ（たとえば vibe.d など）では嬉しい機能ですね。
私もサンプルプロジェクトを色々書くことがあるので、ちょっと使ってみようかと思います。



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

今回は、非推奨化が1件（実質複数？）、廃止はありませんでした。

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

https://dlang.org/deprecate.html

## 非推奨

### `bool` 型の値が `0` または `1` 以外の場合、`@safe` ではなくなりました

`bool` 型は `0` か `1` で表される真偽値型ですが、これまでは `@safe` で `0` または `1` 以外の値を `bool` として扱うことができました。
しかし、今回の変更により、`bool` 型の値が `0` または `1` 以外の場合、`@safe` でないとみなされ警告が出るようになります。
もう少し言えば、「`0` か `1` だと分からない場合に警告が出る」ということです。

具体的には以下のようなケースが `@safe` コードで非推奨となりました。

- `void` での `bool` の初期化（2.109 から）
- `union` からの `bool` フィールドの読み取り（2.109 から）
- 動的配列から `bool` の動的配列への実行時キャスト
- `bool` の動的配列から末尾可変動的配列への実行時キャスト
- ポインタを `bool` ポインタ型にキャストする
- `bool` ポインタを末尾可変ポインタ型にキャストする

確認できた範囲で、サンプルコードは以下のようなものです。末尾可変配列はどのような型かよくわからず、色々試しましたが警告が出ませんでした。

```d
union U {
	bool b;
	int i;
}

void test(U u, int* p) @safe
{
    bool b1 = void; // void での bool 初期化

	bool b = u.b; // union からの bool フィールドの読み取り

    int[] a = [1, 2, 3]; // 動的配列から bool の動的配列への実行時キャスト
	bool[] b3 = cast(bool[]) a;

	bool* b6 = cast(bool*) p; // ポインタを bool ポインタ型にキャストする
}
```

それぞれ、以下のような警告が表示されるようになります。

```
source\app.d(40,10): Deprecation: a `bool` must be 0 or 1, so void intializing it is not allowed in safe functions
source\app.d(42,11): Deprecation: cannot access overlapped field `U.b` with unsafe bit patterns in `@safe` code
source\app.d(42,11): Deprecation: cannot access overlapped field `U.b` with unsafe bit patterns in `@safe` code
source\app.d(45,14): Deprecation: cast from `int[]` to `bool[]` not allowed in safe code
source\app.d(45,14):        Source element may have bytes which are not 0 or 1
source\app.d(47,13): Deprecation: cast from `int*` to `bool*` not allowed in safe code
source\app.d(47,13):        Source element may have bytes which are not 0 or 1
```


警告なのでまだ無視できますが、修正する場合はいくつかの方法が考えられます。

たとえば、 `@safe` をやめて適切なアサーションと共に `@trusted` にするか、 `cast` を使わずに明示的に変換するなどです。
ややトリッキーなものも含めて、警告が出ないようにする対処をいくつか書いてみます。

```d
void test2(U u, int* p) @safe
{
	bool b1 = 0; // 0 または 1 で初期化

	bool b2 = u.i != 0; // 0 かどうかを明示的に判定（0 または 1 以外のビットパターンが 1 になる）

	int[] a = [0, 1, 0];
	import std.algorithm : all;
	assert(a.all!(x => x == 0 || x == 1)); // 0 または 1 以外が含まれていないことをチェック
	bool[] b3 = (() @trusted { return cast(bool[]) a; })(); // @trusted で警告を消す

	assert(*p == 0 || *p == 1); // 0 または 1 であることをチェック
	bool* b6 = (() @trusted { return cast(bool*) p; })(); // @trusted で警告を消す
}
```


# その他

## リリースの遅れについて

さて、今回の記事は前回からなんと約9ヵ月空いています。当初のリリース目標は 2024年8月1日 でした。

このリリースが遅れに遅れた問題は[フォーラムでも質問されたりしている](https://forum.dlang.org/thread/yuxqgozazlfiyiogyjrw@forum.dlang.org)のですが、説明された理由としては2つあるようです。

1. リリース作業を担当するメンテナーの多忙
2. GitHubへの移行に伴うChangeLog生成スクリプトの改修と混乱

一番影響の大きかった要素は、リリース作業を担当するメンテナの多忙、作業の属人性によるものです。
これについて2024年11月末に以下のフォーラムでアナウンス、担当者の仕事が落ち着くのは恐らく来年1月だ、といった話がありました。そして今回なんとか3月にリリースできた、という状況です。

https://forum.dlang.org/thread/xlsnoajvuphefxlestwu@forum.dlang.org

要因の2点目は、Bugzillaで管理していたIssueをGitHubへの移行するプロジェクトの影響です。
2024年8～10月にはコンパイラリリース、2024年12月にIssueの移行、となるはずがリリースの遅れでリリース関連作業と衝突し、ChangeLogを生成する周りのスクリプトの問題が見つかった、という話のようです。

https://forum.dlang.org/thread/xylzmujfgnfnxuhkxarm@forum.dlang.org

これまで20年近く開発されてきたD言語ですが、ここまでの問題は初めてであり、ボトルネックの特定と再発防止に取り組むとのことです。
GitHubでリリースも組めるので、より健全な開発体制になることを期待したいですね。
何か大きな問題が起きているときは他の作業も見直す方が安全、ということで。


# まとめ

しっかりと機能強化や安全性強化が行われているのですが、規模は小さめの印象でした。

おかげで検証も比較的サクッとできましたが、その分次のリリース（2.111.0）と考えられるNightlyが[大変なボリューム](https://dlang.org/changelog/pending.html)になっています。まぁそれは次の機会に…

というわけで久しぶりのリリースでした！強化されたポイントをぜひ活用してみてください！

次のリリースも引き続き注目していきましょう！