D言語の更新まとめ 2023年4月版（dmd 2.103.0）

# はじめに

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

今回は前回からほぼ2ヶ月でのリリースで、やや小粒ながら押さえどころの多いリリースとなっているため内容ご紹介していきます。

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

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

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

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

# 変更点目次

## コンパイラ変更点

- ImportC に `__check(assign-expression)` が追加されました。
- クラスに対する `alias this` は非推奨となりました。
- `-preview=dip25` がデフォルトで有効になりました。
- `export int a;` は `dllimport` ではなく、 `dllexport` を生成するようになりました。
- `__traits(isVirtualFunction)` および `__traits(getVirtualFunctions)` が非推奨になりました。

### 改善点

* Bugzilla 11316: デリゲート引数の型推論が欠けているケースの修正
* Bugzilla 13656: ref型の変数を宣言しようとする際のエラーメッセージを明確化
* Bugzilla 16495: `__traits(fullyQualifedName)` を `std.traits.fullyQualifiedName` の代わりに使用
* Bugzilla 20101: BetterC: CTFEのみのコンテキストでのテンプレートインスタンス化は、コード生成/ nogc / ...フェーズをスキップするべき
* Bugzilla 23558: `__traits(getModuleClasses [, モジュール名])` を追加
* Bugzilla 23597: .diファイルが `-i` オプションと互換性がない

## ランタイム変更点

### 変更点

今回はありませんでした。

### 改善点

* Bugzilla 18765: [配列] 静的配列の要素リテラルでの初期化に関するドキュメント情報が不足
* Bugzilla 20997: スコープガードが `return` 文の後に実行される例が欠けている
* Bugzilla 22418: 文字列に関するドキュメントの誤り
* Bugzilla 22594: "Interfacing to C"のページで `intptr_t` および `uintptr_t` を含めるように更新
* Bugzilla 23612: 記事インデックスにテンプレート制約の記事が表示されない
* Bugzilla 23636: `shared` 修飾子に関する仕様ドキュメントがない
* Bugzilla 23730: IsExpression における `Identifier :` および `== TypeCtor` の仕様を明確化

## ライブラリ変更点

- 書記素(grapheme)の走査ロジックが Unicodeバージョン15 に準拠したものに更新されました。
- `std.algorithm.iteration.joiner` のメッセージが `static assert` でより分かりやすくなりました。
- `std.algorithm.sorting.sort` のメッセージが `static assert` でより分かりやすくなりました。

### 改善点

* Bugzilla 19567: `[std.stdio]` `tell`のドキュメントがあまり役に立たない
* Bugzilla 20397: `[std.algorithm]` `nthPermutation`のドキュメント
* Bugzilla 23683: `std.file.setTimes`が必要以上の権限を要求する
* Bugzilla 23706: 必要でない限りPOSIXシェルパラメータをエスケープしない

## DUB 変更点

- `--color` 引数は、`auto`、`never`、`always` の値を受け付けるようになりました


# 注目トピック

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

### ImportC に `__check(assign-expression)` を追加

D言語には標準で組み込まれている `assert` ですが、Cコードでは通常 `#include <assert.h>` を用いることで利用できるようになります。

今回の機能強化により、D言語のCソースを取り込むImportC機能において、 `#include <assert.h>` をしなくても使える `assert` 相当の機能として `__check(assign-expression)` という式が追加されました。
この `__check` は標準のC言語にはない機能ですので、つまりこちらは「D言語コンパイラにおける、C言語コンパイラ拡張機能」となります。（何を言っているんだ？？？）

ImportCの機能も含めて簡単に使ってみると以下の通りです。通常の `assert` と同じ使い方になります。

```c:lib.c
int test()
{
    int x = 6;
    __check(x > 0); // xが0より大きいことをチェック
    x--;
    __check(x == 4); // xが4であることをチェック(失敗する予定)
    return x;
}
```

```d:app.d
module app;

import std.stdio;
import lib; // いつも通りimportする

void main()
{
    const x = test();
    writeln("test: ", x);
}
```

```console:コンパイル
dmd app.d lib.c
```

```console: エラー
core.exception.AssertError@lib.c(6): Assertion failure
```

エラーメッセージを見るとそのまま `AssertError` ですので、中身は完全にD言語の `assert` です。

コンパイラスイッチ `-checkaction=C` を使用すると、Cの `assert` マクロと同じ動作になります。
また、コンパイラスイッチに `-release` が指定されると、`__check` は単に無視されます。

なお、 `__assert` という名前にすることも考えられたようですが、いくつかのCの `.h` ファイルと競合したため避けられたようです。

これでまたひとつ ImportC の機能が使いやすくなりました。C言語のソースが手元にある方は、こちらもぜひ使ってみてください。


### `export int a;` は `dllimport` ではなく、`dllexport` を生成するようになります

ここで扱う `dllimport` と `dllexport` は、WindowsのDLLを含む共有ライブラリなど、外部で宣言されたシンボルの存在を示したり取り扱うための用語です。

これはD言語ではなく一般論としてですが、`dllimport` と `dllexport` について少し整理しておくと、以下のような使い分けをします。

- `dllimport` は、あるDLLから別のDLLや実行可能ファイルがシンボルを利用する際に使用する（外部の定義を使うだけである場合）
- `dllexport` は、DLL内で定義されたシンボルを外部の実行可能ファイルやDLLに公開するために使用する（自分が定義して外部の誰かが使う場合）

これらに対してD言語では、こういった外部シンボルを扱うために `export` というキーワードが用意されていました。

今回この挙動が一部修正され、`export int a;` といういかにも外部公開されそうな記述で `dllimport` になっていたものが、名前通り `dllexport` になります。

結果として、以下のような目的別の使い分けになります。

```d:DLLを作ってシンボルを外部から使えるようにしたい(dllexportしたい)場合
export int a;
export void f() { }
```

```d:外部のシンボルを読み込んで使いたい(dllimportしたい)場合
export extern int a;
export void g();
```

関数宣言のところは本体の有無で変わりますが、外部のシンボルを参照するときに本体書けないことを思いだせば、それぞれ相応の振舞いになるので特に問題は起きないと思います。

元々 `export int a;` と書いていた方は、 `export extern int a;` と書き換える必要があります。


ちなみに、類似のキーワードで `extern` を単体で指定することもできます。（ややこしい）
詳しい説明をすると長くなりそうなので、気になる方は下記リンクを参照してみてください。

__extern参考__

https://dlang.org/spec/declaration.html#extern


### `__traits(getModuleClasses [, モジュール名])` が追加されました

ChangeLogには細かい記載がありませんが、改善点のほうに書かれていた内容で少し重要そうなのでご紹介します。

こちら、現在のモジュールか指定したモジュールからクラス定義を得るための `__traits` となっています。
以下のように使います。

```d
module test;
import std.stdio;
class C { }
pragma(msg, __traits(getModuleClasses)); // tuple("test.C")
pragma(msg, __traits(getModuleClasses, std.stdio)); // tuple("std.stdio.StdioException")
```

第2引数がない場合は現在のモジュールのクラスを得て、第2引数にモジュールを指定すると定義されたクラスを得ることができます。

何のために追加されたかといえば `Object.factory` というクラスの実行時情報型を得るための関数があるのですが、これの代替です。
`Object.factory` を使おうとしたとき、非推奨のメッセージとしてこちらの `__traits(getModuleClasses)` を使うように推奨される内容が表示されます。

```d
module object;

class Object {
    deprecated("use __traits(getModuleClasses) instead")
    static Object factory(string classname) {
        // 省略
    }
}
```

こちらは `__traits` なので静的(コンパイル時)に決まる範囲でしか使えませんが、これはこれで強力な使い方が出来そうであるので、思いついた方はぜひ使ってみてください。


## ライブラリ変更点

### 書記素(grapheme)の走査ロジックが Unicodeバージョン15 に準拠したものに更新されました。

書記素(grapheme)とは、テキストにおいて意味を区別する最小の視覚的な単位のことです。(一方で「1文字」を表現する単位は「コードポイント」と呼ばれます)

参考 : https://ja.wikipedia.org/wiki/%E6%9B%B8%E8%A8%98%E7%B4%A0

D言語の文字列をこの書記素単位で処理する関数としては、`std.uni` の `graphemeStride`、`byGrapheme`、および `decodeGrapheme` 関数などがあります。
これらは、古いUnicode規格の時代のルールを使用していましたが、今回書記素の分割ルールがUnicodeバージョン15に対応するように更新されました。

これにより、Phobosの各関数は拡張された絵文字（例：国旗や家族の絵文字）や、特定の言語で使われる修飾文字（例：アクセント記号）を認識するようになりました。この変更により、より正確にテキストを処理できるようになります。

テキスト処理の需要も伸びてきている気がしますので、こういった強化への追従は重要ですね。
書記素は便利な概念であるので個人的にも今後使っていきたいと思います。

### `std.algorithm.iteration.joiner` と `std.algorithm.sorting.sort` のメッセージが `static assert` によりわかりやすくなります

コードを読む方が早そうな話であるので、 `joiner` のほうからソース抜粋します。

元々以下の定義でした。

```d:元の定義
auto joiner(RoR, Separator)(RoR r, Separator sep)
if (isInputRange!RoR && isInputRange!(ElementType!RoR)
        && isForwardRange!Separator
        && is(ElementType!Separator : ElementType!(ElementType!RoR)))
{
    // 省略
}
```

改善後はこうなっています。

```d:改善した定義
auto joiner(RoR, Separator)(RoR r, Separator sep)
{
    static assert(isInputRange!RoR, "The type of RoR '", RoR.stringof
            , " must be an InputRange (isInputRange!", RoR.stringof, ").");
    static assert(isInputRange!(ElementType!RoR), "The ElementyType of RoR '"
            , ElementType!(RoR).stringof, "' must be an InputRange "
            , "(isInputRange!(ElementType!(", RoR.stringof , "))).");
    static assert(isForwardRange!Separator, "The type of the Seperator '"
            , Seperator.stringof, "' must be a ForwardRange (isForwardRange!("
            , Seperator.stringof, ")).");
    static assert(is(ElementType!Separator : ElementType!(ElementType!RoR))
            , "The type of the elements of the separator range does not match "
            , "the type of the elements that are joined. Separator type '"
            , ElementType!(Separator).stringof, "' is not implicitly"
            , "convertible to range element type '"
            , ElementType!(ElementType!RoR).stringof, "' (is(ElementType!"
            , Separator.stringof, " : ElementType!(ElementType!", RoR.stringof
            , "))).");

    // 省略
}
```

テンプレート関数の後についていた `if` のあたりが消えて、`static assert` がガッツリ増えました。
この消えた `if` は「テンプレート制約(Template constraints)」と呼ばれ、テンプレートの一致性を判定するための機能となっています。

今回やっている内容は、「テンプレート制約に反したときのエラーで何に違反したかわかりづらい。問題個所をわかりやすくするため `static assert` で書き直した」ということです。

ここで注意すべき点ですが、テンプレート制約を安易に `static assert` に書き換えることは推奨されません。

テンプレート制約でしか実現できないこととして、「同名のテンプレートを別の定義として書いてマッチするものを選択する」というときに効いてきます。これを目的とした場合、 `static assert` で書くとかなり複雑なことになります。

書き換えられる条件を考えてみると「1つしかない定義であり、今後増える見込みもない」という場合に限って書き換えを検討すると良さそうです。

標準ライブラリは特に親切なメッセージを出してもらいたいですが、使う側としても一つ参考にしていきたい内容ですね！


## DUB変更点

### --color 引数は、auto、never、always の値を受け付けるようになりました

Linuxツールとの互換性を保つため、より一般的に使われている `auto`、`never`、`always` というフラグ値が使えるようになりました。

今までは `automatic`, `on`, `off` だったのですが、こちらもこれまで通り利用できます。（ただしドキュメントには記載されません。記載されるのは `auto/never/always` です）

フラグの意味はもう完全に名前の通りですが、表にしてみると以下のような対応付けになります。

| 元のフラグ | 更新されたフラグ名 | フラグの説明                                                         |
|:-----------|:-------------------|:---------------------------------------------------------------------|
| automatic  | auto               | コンソールがカラーをサポートしている場合、カラー出力を有効にします。 |
| on         | always             | 常にカラー出力を有効にします。                                       |
| off        | never              | カラー出力を無効にします。                                           |


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

今回は、非推奨が2件、廃止が1件で計3件の機能が非推奨および廃止となります。

余り影響のないものが多いですが、一部わかりづらいものもありますので一つ一つ解説していきます。

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

https://dlang.org/deprecate.html

__非推奨__
- クラスに対する `alias this` は非推奨になりました。
- `__traits(isVirtualFunction)` および `__traits(getVirtualFunctions)` が非推奨になりました。

__削除/エラー__
- `-preview=dip25` がデフォルトで有効になり、エラーを発生させる場合があります。


### 非推奨 : クラスに対する `alias this` は非推奨になりました。

今回のリリースの中でも、やや驚きがある反響の大きいトピックです。

`alias this` を簡単に説明すると、「あるオブジェクトを定義したとき、それが持つフィールドに暗黙変換を認める簡略記法」です。

```d
struct Test {
    int value;

    alias value this;
}

void hoge(int n) {
    writeln(n);
}

void main() {
    auto obj = Test(100);
    hoge(obj); // obj は Test だが、intに暗黙変換される
}
```

この `alias this` ですが、実は `struct` と `union` に限定された仕様でした、というのが今回の発端です。
一方のクラスですが、クラスに対する `alias this` の仕様は明確に定義されておらず、適切な変換ルールが明らかではありませんでした。

そのため、クラスで `alias this` を使用すると、さまざまなエラーやクラッシュが発生することがあったようです。
そういった経緯から、このリリースでクラスに対する `alias this` が明確に非推奨となります。

```console:違反した際のメッセージ
Deprecation: alias this for classes/interfaces is deprecated
```

この警告メッセージに遭遇した場合、正攻法でいけば `alias this` を削除したうえで、更に発生する暗黙変換できないエラーに対処する必要があります。

```d:元ソースイメージ
import std.stdio;

void main()
{
    auto t = new Test;
    t.child = new Child;
    t.child.name = "test";

    hoge(t);
}

class Child
{
    string name;
}

class Test
{
    Child child;

    alias child this; // ここに alias this があるので消す
}

void hoge(Child n)
{
    writeln(n.name);
}
```

```console:alias-thisを消すと出るエラーメッセージ
app.d(11): Error: function `app.hoge(Child n)` is not callable using argument types `(Test)`
app.d(11):        cannot pass argument `t` of type `app.Test` to parameter `Child n`
```

暗黙変換できていたところを消すわけなので、適切にプロパティを明示すれば解決します。

```d:修正版main
void main()
{
    auto t = new Test;
    t.child = new Child;
    t.child.name = "test";

    hoge(t.child); // .child を付ける
}
```

なお、公式のChangeLogだと別の方法が記載されていますが、`static foreach` でフィールドのプロパティに転送する定義を色々作ってしまおう、というやや強引な方法でしたのでここでは紹介しません。

類似の方法で `std.typecons.Proxy` という `mixin` による自動化もあるのでこちら検討していただくと良いと思います。

https://dlang.org/phobos/std_typecons.html#Proxy

### 非推奨 : `__traits(isVirtualFunction)` および `__traits(getVirtualFunctions)` が非推奨になりました。

このリリースまで、D言語には `__traits(isVirtualFunction)` と `__traits(isVirtualMethod)` （およびそれぞれに対応する `__traits(get...)`）がありました。

`isVirtualFunction` は、引数が仮想関数である場合に `true` を返し、それ以外の場合は `false` を返します。ただし、何もオーバーライドしない `final` 関数に対しては `true` を返します。
`isVirtualMethod` は、引数が仮想関数である場合に `true` を返し、それ以外の場合は `false` を返します。ただし、何もオーバーライドしない `final` 関数に対しては `false` を返します。

一見オーバライドの部分以外は同じですが、この `isVirtualFunction` はD言語の仕様と矛盾した振る舞いでした。D言語では、関数をオーバーライドしない `final` 関数は仮想関数にならないためです。

この場合、 `isVirtualMethod` は正しく `false` を返しますが、 `isVirtualFunction` は `true` を返してしまいます。

仕様に反した振舞いということで、このリリースから、`__traits(isVirtualFunction)` および `__traits(getVirtualFunctions)` の両方が非推奨となります。

元の振舞いを実現したい場合は、以下のように書くことができます。

```d
__traits(isVirtualMethod, f) || (__traits(isFinalFunction, f) && !__traits(isOverrideFunction, f))
```

今後は、`isVirtualFunction` と `getVirtualFunctions` の使用は避け、`isVirtualMethod` を使用してください。


### `-preview=dip25` がデフォルトで有効になり、エラーを発生させる場合があります。

DIP25というD言語改善提案が正式に有効になりました。

DIPの内容確認のため、公式のソースを持ってくると以下のような内容になります。


```d
ref int escapeRef(ref int x) { return x; }

// 以前は：
// Deprecation: returning x escapes a reference to parameter x
// perhaps annotate the parameter with return
//
// 現在は、エラーが報告されます。ただし、-revert=dip25が使用された場合を除きます。
```

元々DIP25はこれまで非推奨でした。これは2.092.0(2020/05/10リリース)以降非推奨となっています。
これより前のバージョンからコンパイラのバージョンを上げると突然にエラーになったように見えるかもしれません。

これを修正回避するには、関数の引数が `return ref` となるように修飾を行うか、 `-revert=dip25` を付けてください。

また非推奨になった当時の機能説明がありますので、詳細気になる場合は以下の記事を参考としていただければと思います。

https://qiita.com/lempiji/items/03cade867b6af41d2e1d#%E5%A4%89%E6%9B%B4-dip25%E3%81%8C%E6%97%A2%E5%AE%9A%E3%81%A7%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F

# まとめ

リリースとしては比較的小規模ですが、ImportCの強化で `__check` といった独自拡張を入れられる程度には成熟してきたのかな、という印象の今リリースでした。

一方で非推奨や挙動変更もあり、やや注意が必要なバージョンと言えそうです。（ある意味小規模で良かった？）
メッセージが親切になるなど嬉しい面もありますが、 `dllimport` を使っていたり、DIP25のエラーに引っかかるような場合は対処方法をしっかり押さえておく必要があります。

また今回リリースサイクルも2ヶ月サイクルに復帰しつつあるようですので、次回は順調にいけば6月です。

次のリリースでどのような話題が出てくるのか、期待して待ちたいと思います！