D言語の更新まとめ 2020年7月版（dmd 2.093.0）

# はじめに
D言語の公式コンパイラである dmd が更新され、最新バージョンである `2.093.0` が 2020/07/7 にリリースされました。

今回は比較的控えめのアップデートなので、リリースノートの内容全種紹介していきたいと思います。
※ 2020/07/12追記 ベータが取れて正式版になったところ、改善点がだいぶ増えたので抜粋に戻します

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

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

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

- D言語の更新まとめ 2020年5月版（dmd 2.092.0）
    - https://qiita.com/lempiji/items/03cade867b6af41d2e1d

# 変更点目次

## コンパイラ変更点
- ビルトインの `__traits(isCopyable, T)` が追加されました
- インターフェースの実装が適切にチェックされるようになりました
- `shared` 変数の初期化が許可されるようになりました
- templateの統計を収集、一覧化するための `-vtemplates` スイッチが追加されました

### コンパイラ強化点
※2020/07/12 追記

- Bugzilla 20791: `extern(C++)` が末尾のコンマを許容するようになりました
- Bugzilla 20796: `package(x.y)` 形式の保護属性が宣言と同じモジュールを許容するようになりました

## ランタイム変更点
- `core.sys.linux.unistd` に `exit_group` が追加されました 
- 新たなモジュールとして `core.sys.darwin.mach.nlist` と `core.sys.darwin.mach.stab` が追加されました
- `object.selector` が非推奨になりました
- メモリ関連のGCオプションがより高精度で指定できるようになりました

### ランタイム強化点
※2020/07/12 追記

- Bugzilla 20746: ガベージコレクタで使われる LCG(線形合同法) で状態bitのうち48bitしか使われなかったものが64bitすべてを使うようになります
- Bugzilla 20787: `core.sys.darwin.sys.attr` が追加されました
- Bugzilla 20844: `cas` の呼び出し後、データのアラインメントを考慮するようになります
- Bugzilla 20859: `ReadWriteMutex` の `tryLock` にタイムアウトを指定できるようになりました


## ライブラリ変更点

### ライブラリ強化点
- Bugzilla 19525: `Duration` に対する `std.algorithm.sum` の引数なし呼び出し時エラーが改善されました
- Bugzilla 20496: `chunkBy` が `ref` 引数による述語をサポートします
- Bugzilla 20869: `std.algorithm.mutation` の `move` が `onPostMove` の属性を考慮するようになりました
- Bugzilla 20887: 配列でないレンジに対する `std.digest.digest!(Hash, Range)` の速度が改善されました


## インストーラー変更点
- インストールスクリプトが Windows のコマンドプロンプトで動作するようになりました

## DUB変更点
- ルートパッケージのターゲットに関する環境変数が追加されました
- CLIの拡張性が改善されました

## ツール変更点

### ツール強化点
※2020/07/12 追記

- `rdmd --eval` が引数をサポートするようになりました

# 注目トピック
## 言語・コンパイラ変更点
### 強化：ビルトインの `__traits(isCopyable, T)` が追加されました

型 `T` がコピー可能かどうかを確かめる `__traits` が増えました。

通常の値型や参照型はコピー可能で `true` 、 `@disable this(this);` でコピー禁止にすると `false` になる、というものです。

元々 `__traits(compiles, {})` を使った書き方でも実現できていて、 `std.traits` にも同名の `isCopyable` テンプレートがありましたが、利便性等の面からビルトインの機能として提供されるようになりました。

### 修正：インターフェースの実装が適切にチェックされるようになりました

インターフェースが要求するメソッドをすべて実装しているチェックが正しく動作するようになりました。

といっても無条件に起きるバグではなく、ドキュメント生成やインターフェース定義ファイル(.di)を出力するときによく使う `-o-` スイッチ（オブジェクトファイルを生成しなくする）を付けるとチェックがスキップされてしまうという問題です。

元々コンパイルフェーズの後半に行われており、コンパイルの処理順序を見直して前半に移動することで修正された格好です。

もし `-o-` スイッチを使っていることがあればエラーにならないか一度チェックしておくと良いかと思います。

```d:チェックされない例
interface Foo
{
    void foo();
}

class Bar : Foo
{
    // void foo() { }
}
```

### 改善：`shared` 変数の初期化が許可されるようになりました

別に `shared` 変数普通に初期化できるじゃん…？と思っていたのですが、
実際は `-preview=nosharedaccess` を使っているときのロジック改善でした。
（ChangeLogにちゃんと書いてありました）

このスイッチは、今年1月に採択された DIP1024 で定義される「`shared` 変数への直接アクセスを禁止する」という変更を先取りするためのものです。
（`core.atomic` の `atomicLoad` や `atomicStore` 、 `atomicOp` 等を使うことを強制する）

- DIP1024
    - https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md

たとえばフラグを付けると以下のようなコードでコンパイルエラーが発生しましたが、初期化の時点でなんでやねん、という話なので許可されます、という話です。

```d:
void main()
{
    shared int n;
    shared int m = 3;
}
```

```console:
onlineapp.d(3): Error: direct access to shared n is not allowed, see core.atomic
onlineapp.d(4): Error: direct access to shared m is not allowed, see core.atomic
```



### 強化：templateの統計を収集、一覧化するための `-vtemplates` スイッチが追加されました

テンプレートの特殊化が何種類、計何回行われたか、を示す統計情報が出力できる `-vtemplates` フラグが追加されました。
`-vtls` など、 `-v` で始まる系のフラグは情報表示が増えるシリーズなので覚えておくと良さそうです。

試しに `import std.stdio;` するだけのコードで出してみると以下のようになります。

```d:
import std.stdio;
void main() { }
```

```console:
  Number   Unique   Name
       1        1   isFunctionPointer(T...) if (T.length == 1)
       2        1   at(R)(R[] r, size_t i)
       1        1   isDynamicArray(T)
       1        1   Impl(T)
       1        1   isDelegate(T...) if (T.length == 1)
       1        1   atomicFetchSub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, size_t mod) if (__traits(isIntegral, T) || is(T == U*, U))
       1        1   IntOrLong(T)
       3        1   isAggregateType(T)
       3        1   TailShared(U) if (!is(U == shared))
       1        1   needsLoadBarrier(MemoryOrder ms)
       1        1   atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))
      13        1   hasUnsharedIndirections(T)
       7        2   atomicValueIsProperlyAligned(T)(ref T val)
       1        1   atomicFetchAdd(MemoryOrder ms = MemoryOrder.seq, T)(ref T val, size_t mod) if ((__traits(isIntegral, T) || is(T == U*, U)) && !is(T == shared))
       2        1   atomicFetchAdd(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, size_t mod) if (__traits(isIntegral, T) || is(T == U*, U))
       1        1   atomicFetchSub(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) if (is(T : ulong))
       2        1   atomicFetchAdd(MemoryOrder order = MemoryOrder.seq, bool result = true, T)(T* dest, T value) if (is(T : ulong))
      71        1   __equals(T1, T2)(T1[] lhs, T2[] rhs)
       1        1   atomicLoad(MemoryOrder order = MemoryOrder.seq, T)(inout(T)* src) if (CanCAS!T)
       3        2   RegIndex(T)
       2        2   atomicPtrIsProperlyAligned(T)(T* ptr)
       1        1   classInstanceAlignment(T) if (is(T == class))
       3        3   SizedReg(int reg, T = size_t)
       1        1   TailShared(S) if (is(S == shared))
       3        2   Unqual(T)
       1        1   OriginalType(T)
       9        3   atomicOp(string op, T, V1)(ref shared T val, V1 mod) if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod")))
       3        3   makeGlobal(StdFileHandle _iob)()
       6        4   Flag(string name)
       1        1   ModifyTypePreservingTQ(alias Modifier, T)
       3        3   maxAlignment(U...)
       1        1   CanCAS(T)
       1        1   isPointer(T)
       6        1   atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(ref const shared T val) if (!hasUnsharedIndirections!T)
       2        1   Demangle(T)
       1        1   atomicFetchSub(MemoryOrder ms = MemoryOrder.seq, T)(ref T val, size_t mod) if ((__traits(isIntegral, T) || is(T == U*, U)) && !is(T == shared))
       1        1   DynamicArrayTypeOf(T)
```

本気で分析するならもう少し細かい情報が欲しい気もしますが、元々 `std.meta` の `Filter` 等をunrollして高速化する話があり、そこで得られた知見のフィードバックといったところでしょうか。
単純に特殊化の回数を減らす目的としては十分な情報だったようです。

コンパイル時間の改善にもつながりますし、メタメタしたプログラムを書く方には非常におすすめの機能なのでぜひ1度試してみてください。

思わぬテンプレートが見つかるなどの発見があるかもしれません。

### 改善: `extern(C++)` が末尾のコンマを許容するようになりました
※2020/07/12 追記

以下のような記述が許容されるようになります。
メタプログラミングや自動生成するときに少しさぼれますし、たくさんあるときの改行や追加時の差分が見やすいので良いですね。

```d:
extern(C++, "foo",)
```

※こういう文法周りの改善は地味でもシンタックスハイライトが追い付かなかったりすると大変そうですね、、


### 改善: `package(x.y)` 形式の保護属性が宣言と同じモジュールを許容するようになりました
※2020/07/12 追記

`package` 保護属性というのは、同一パッケージ内、または他の指定したモジュールからだけ参照させる指定ができるアクセス保護属性です。

この改善は、同一パッケージ内でだけ参照できる、つまり `private` と同様の働きを `package` 保護属性でも実現できるようになります。
メタプログラミングとかの都合でしょうか、あまり使う機会はないかもしれませんが、自由度が上がったので良いことだと思います。

```d:
module somelib;

package(somelib) void foo() { }
```


## ランタイム変更点

### 強化： `core.sys.linux.unistd` に `exit_group` が追加されました 

見出しの通りです。
Linux 2.5.35以降で使える、現在のプロセス内の全スレッドを終了させるシステムコールです。


### 強化： 新たなモジュールとして `core.sys.darwin.mach.nlist` と `core.sys.darwin.mach.stab` が追加されました

MacOSX向けで、それぞれC/C++向けヘッダーの `mach-o/nlist.h` と `mach-o/stab.h` に相当します。

MacOSX 10.15 (Catalina) のSDKで追加されたものということで、2019年10月リリースの2020年6月時点では最新のSDKまで対応が進んできた、ということになります。



### 強化： メモリ関連のGCオプションがより高精度で指定できるようになりました

メモリの少ないデバイス向けの強化で、各種メモリのサイズ指定に `B`, `K`, `M`, `G` といった接尾語を付けることで細かい指定ができるようになりました。

GCオプションはプログラム中に埋め込む方法と実行時に与える方法がありますが、それぞれ以下のようになります。

```d:埋め込み
extern(C) __gshared string[] rt_options =
    [ "gcopt=minPoolSize:4K maxPoolSize:2M incPoolSize:8K" ];
```

```console:実行時
app "--DRT-gcopt=minPoolSize:4K"
```

今まで通り数字だけ指定した場合はメガバイト単位での指定になります。

### 強化: `ReadWriteMutex` の `tryLock` にタイムアウトを指定できるようになりました
※2020/07/12 追記

`ReadWriteMutex` は、データの読み取りを管理する `Reader` と書き込みを管理する `Writer` が協調するロック機構です。

読み取りと書き込みを分けることでデータ競合が起きないことを保証でき、排他される機会を減らし効率化できます。
詳細はこちら。

- `core.sync.rwmutex`
    - https://dlang.org/phobos/core_sync_rwmutex.html

今回追加されたのは、`lock` が取得できるか試すために使う `tryLock` で、これに引数で時間間隔を表す `Duration` が指定できるようになりました。

```d:
auto rwmutex = new ReadWriteMutex;

const locked = rwmutex.writer.tryLock(1.seconds); // 1秒以内にロックがほしい
if (locked) {
    scope (exit) rwmutex.writer.unlock();
    // データの書き込み
}
```

`tryLock` で一定時間待つような実装をしている場合、これで置き換えることができます。
記述が簡単になるのはありがたいですね。


## ライブラリ変更点

### 改善: `Duration` に対する `std.algorithm.sum` の引数なし呼び出しができるようになりました
※2020/07/12 追記

元々、以下のようなコードがエラーになっていました。

```d:
auto s = [1.minutes, 1.minutes].sum();
assert(s == 2.minutes);
```

これは `Duration` がやや特殊な構造体で、単一の数値を表す構造体にもかかわらず `Duration(0)` という形式でゼロに対応する値が作れない、ということに起因しています。
（`Duration` の `0` は `Duration.zero` で取得します）

これが今回から `.zero` のプロパティを考慮するようになり、上記コードも動くようになります。


### 強化: `chunkBy` が `ref` 引数による述語をサポートします
※2020/07/12 追記

`chunkBy` というのは配列などをブロック毎に区切るための関数です。

```d:基本の使い方
auto s = [1, 1, 1, 2, 2, 2, 3, 3, 3];
writeln(s.chunkBy!((e1, e2) => e1 == e2));
```

```d:結果
[[1, 1, 1], [2, 2, 2], [3, 3, 3]]
```

今回から以下のように `ref` がつけられるようになります。これで効率化されるシーンがあるかもしれません。

```d:
auto s = [1, 1, 1, 2, 2, 2, 3, 3, 3];
writeln(s.chunkBy!((ref e1, ref e2) => e1 == e2));
```

### 改善: `std.algorithm.mutation` の `move` が `onPostMove` の属性を考慮するようになりました
※2020/07/12 追記

あまり使うことはないかもしれませんが、`move` はムーブ対象がコピー可能な場合、そのオブジェクトの `onPostMove` というメソッドを実行するようになっています。


この部分が今まで `@trusted` を付けた実装になっており、`opPostMove` が `@system` でも無視されてしまっていました。
これがコンパイラによって正しくチェックされるようになり、以下のようなコードがエラーになるようになります。

```d:
import std.algorithm.mutation;

struct S
{
    void opPostMove(const ref S old) @system nothrow pure
    {
        assert(a == old.a);
        a++;
    }
    int a;
}

void main () @safe nothrow
{
    S s1;
    s1.a = 41;
    S s2 = move(s1);
    assert(s2.a == 42);
}
```



### 改善: 配列でないレンジに対する `std.digest.digest!(Hash, Range)` の速度が改善されました
※2020/07/12 追記

これは以下の要領で任意のRangeデータからハッシュが求められる関数です。

```d:
import std.digest.md;
import std.range : repeat;
auto testRange = repeat!ubyte(cast(ubyte)'a', 100);
auto md5 = digest!MD5(testRange);
```

速度改善の結果や差分は以下にまとまっています。
愚直にパターン毎の実装を切り替え、全体的に実行時間が半分以下になった、という感じです。

https://github.com/dlang/phobos/pull/7509


## インストーラー変更点

### 強化： インストールスクリプトが Windows のコマンドプロンプトで動作するようになりました 

文字通りの強化です。
インストールスクリプトというのは dub や dmd をコマンド実行でインストールする仕組みのことです。

インストールスクリプトの説明は以下のあたりが詳しいです。

- https://qiita.com/outlandkarasu@github/items/faa555d5c1d1d19a8fa4
- https://blog.kotet.jp/2017/12/d-install-script/


ただ、`install.sh` というファイルはそのままで、特に `install.bat` ができたわけでも `install.sh` をダブルクリックして実行できるわけでもありません。

どういうことかというと、今までは POSIX のターミナルエミュレータでしか動かすことができなかったものが、MSYS2がインストールされている場合、それを使ってコマンドプロンプトから実行できるようになった、という強化です。

`MSYS2` が `C:\msys64` にインストールされているとして、以下のように実行できます。

```console:準備
C:\msys64\usr\bin\pacman.exe --sync unzip p7zip
```

```console:インストール
C:\msys64\usr\bin\curl.exe https://dlang.org/install.sh | \msys64\usr\bin\bash.exe -s
```

```console:ヘルプ
C:\msys64\usr\bin\bash.exe %USERPROFILE%\dlang\install.sh --help
```

ガッツリbash等使っており、要するにLinux環境と同じことをやっているだけです。
ただ、わざわざターミナル起動しなくて良いので、batファイルとかにまとめれば良いのはありがたいですね。

差分はこちら。

- https://github.com/dlang/installer/commit/e084815001390538bbd6fe5be7c2d4d81ee681b7

対応されたのは Extended-Pascal から D へのトランスパイラを書いている Bastiaan Veelo という方です。
DConf 2019の登壇者の方ですね。

- https://qiita.com/lempiji/items/8feadc1c15482b8bbb0e#transcompilation-into-d

## DUB変更点

### 強化： ルートパッケージのターゲットに関する環境変数が追加されました

dubの設定ファイルで使える以下の環境変数が追加されました。

いずれもルートパッケージの情報を取得できるもので、
有り体に言って、参照された側が参照している側（大元）について情報を取得できます

- `DUB_ROOT_PACKAGE_TARGET_TYPE`
    - ターゲット種別、実行ファイルかライブラリか等
- `DUB_ROOT_PACKAGE_TARGET_PATH`
    - ターゲットパス、ルートパッケージの設定ファイルのある場所
- `DUB_ROOT_PACKAGE_TARGET_NAME`
    - パッケージ名

ターゲット種別が分かるのは結構ありがたいですね。
単体テスト系のライブラリで動きを変えるとか色々使い道がありそうです。

### 強化： CLIの拡張性が改善されました

DUBをライブラリとして利用している方向けの強化です。
主にコマンドを追加しやすくするため、引数解析周りの改善が行われたというものです。

`dub` のようにいくつかのサブコマンドを持っていたりすると、 `--` で区切って実行時に渡す引数と分けることがあります。

これは単体テストを見ると分かりますが、引数を以下のように分類して扱うものです。

| コマンド          | コマンド引数     | アプリケーション引数 |
|:------------------|:-----------------|:---------------------|
| `app --version`   | なし             | `--version`          |
| `app a b`         | `a`, `b`         | なし                 |
| `app a --version` | `a`, `--version` | なし                 |
| `dub -- a`        | なし             | `a`                  |
| `app 1 2 -- a`    | `1`, `2`         | `a`                  |

実装としては、 `CommandArgs` クラスに `hasAppArgs` と `appArgs` という「アプリケーション用の引数」に関するメソッドが追加されています。


実際の差分はこちらです。

- https://github.com/dlang/dub/pull/1906



## ツール変更点

### 強化： `rdmd --eval` で引数が渡せるようになりました
※2020/07/12 追記

`rdmd --eval` は、渡した文字列をコンパイルして実行するところまで一発でやってくれる補助ツールです。

`rdmd` に `--eval` という引数で文字列を渡すとそのまま実行してくれます。

```
rdmd --eval="writeln(`Hello, world!`);"
```

これに動的な要素を埋め込もうと思うと、ひと手間エスケープする必要があって面倒でした。
今後は `--` で区切ったあとの引数が `string[] args` が使えるようになり、より簡単に動的な要素が埋め込めます。

```:引数を使う例
rdmd --eval="writeln(args[1 .. $]);" -- a b c
```

```:結果
["a", "b", "c"]
```

ターミナルやコマンドプロンプトからちょっと呼ぶのに連携しやすくなった感じですね。


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

今回の更新では、あまり使われない機能の非推奨が1つだけです。非常に安定していますね。（フラグ）

__非推奨__

- `object.selector` が非推奨になりました

#### 非推奨： `object.selector` が非推奨になりました

これは何かというと `Objective-C` 連携するときに使うものです。
UDAとして付与することにより、メソッドの定義に「セレクタ」（メソッドを特定する内部表現）の情報を付け足します。

今後は `core.attributes` をimportすれば同じように使うことができます。
（ただしバージョン識別子として `D_ObjectiveC` を定義した場合のみ）

~~滅多に使われない割に、~~必ず import される object モジュールに定義されていました。
今回コンパイラ実装の範囲からランタイム実装のほうに移動された格好になります。

```d:
import core.attributes : selector;

extern (Objective-C)
extern class NSObject
{
    static NSObject alloc() @selector("alloc");
}

extern (Objective-C)
extern class NSString
{
    NSString initWith(in char*) @selector("initWithUTF8String:");
    NSString initWith(NSString) @selector("initWithString:");
}
```


このあたりの仕様は以下を参照ください。

- Interfacing to Objective-C
    - https://dlang.org/spec/objc_interface.html#selector-attribute


# まとめ

比較的小規模なバージョンアップとなっていたので全機能紹介してみました。

今回はライブラリの強化はありませんが、地味にパフォーマンス改善などがは行われているほか、5月版で実験的に追加された所有権周りも幾分賢くなっているようです。

前回のリリースからこの2ヶ月、リソースとしては言語の強化に向けたDIPのチェック等に振り向けられていた印象ですが、特に遅れることもなく無事にリリースされそうです。

ベータ版が取れたら改めて更新したいと思います。

次回リリースは9月1日予定、引き続きの強化を期待したいと思います！