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

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

強化の規模は比較的小さめですが、トピックになるものがあるので触れておきたいと思います。

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

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

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

- D言語の更新まとめ 2019年11月版（dmd 2.089.0）
    - https://qiita.com/lempiji/items/b8fc61817446027632dc

# 変更点目次

## コンパイラ変更点
- `lazy` 引数から `delegate` が得られるようになりました。

### 改善点
* Bugzilla 4544: `string` を期待する箇所で文字定数が渡された場合のエラーメッセージが改善されました。
* Bugzilla 11038: `static` を `import` に対してブロック構文で使えるように改善されました。
* Bugzilla 18809: 存在しないプロパティに対するエラーメッセージが改善されました。
* Bugzilla 20334: `posix.mak` の `clean` が生成したファイルをすべて削除するよう改善されました。
* Bugzilla 20448: テンプレート関数からメンバーの参照を返す場合のエラーメッセージが改善されました。

## ランタイム変更点
- `core.memory.GC.inFinalizer` を追加しました。
- プラットフォーム依存な `execinfo` のための機能を追加しました。
- 浮動小数点を固有精度に丸めるための `toPrec` を追加しました。
- `-unittest` 指定時は単体テストのみ行うように変更しました。

### 改善点
* Bugzilla 17563: `gc_inFinalizer` を `public` に変更しました。

## ライブラリ変更点
- 非推奨だった `std.array.Appender.toString` が削除されました。
- 非推奨だった `std.functional.binaryReverseArgs` が削除されました。
- 非推奨だった `std.bitmanip.BitArray.toString` が削除されました。
- 非推奨だった `std.experimental.all` モジュールが削除されました。
- `std.json` に値取得のための `get!(T)` が追加されました。

### 改善点
* Bugzilla 20198: `float` と `double` に対して、`std.math` の `nextUp`, `nextDown`, `nextafter` がCTFEで動作するように改善
* Bugzilla 20288: `std.format` が `NaN` のときの動作を改善
* Bugzilla 20425: `Proxy` の `opCmp` がオーバーロードされた `opCmp` でコンパイルに失敗する問題を改善
* Bugzilla 20439: `memoize` が `void` を返す `opAssign` で失敗する問題を改善

# 注目トピック
## 言語・コンパイラ変更点
### 強化: `lazy` な引数が `delegate` に変換可能になりました

昨年11月の更新(2.089.0)で使えるようになっていた機能ですが、ドキュメントなどを揃えて正式な仕様としてまとまりました。

紹介：https://qiita.com/lempiji/items/b8fc61817446027632dc#改善-lazyな引数が-delegate-に変換可能になります
仕様：https://dlang.org/spec/function.html#lazy-params


以下のように、変換するときは `delegate` だと思って `&` をつける感じです。
便利に使っていきたいですね。

```d:
void test(lazy int dg)
{
    int delegate() dg_ = &dg;
    assert(dg_() == 7);
    assert(dg == dg_());
}

void main()
{
    int a = 7;
    test(a);
}
```



## ランタイム変更点
### 強化： `core.memory` に `GC.inFinalizer` が追加
GCの状態を取得する関数として、「ファイナライザの実行中かどうか」を取得できるようになりました。
「ファイナライザ」というのは普段あまり聞きませんが、要するに「**GCが**使われないオブジェクトを破棄するときに行う処理」のことです。

通常オブジェクトの破棄というと「デストラクタ」ですが、デストラクタは「**GCに限らず**、誰かがオブジェクトを破棄するときに行う処理」のことです。

Dにおいて、ファイナライザとデストラクタは大きな区別がありませんでしたが、デストラクタの中でファイナライザとして実行しているのか判断するために今回設けられたのが `GC.inFinalizer` です。

逆に「GC以外にオブジェクトを破棄する方法があるのか？」という話になりますが、オブジェクトの破棄にはランタイムの `object` モジュールが持つ `destroy` 関数や `scope` ストレージクラスを付けた変数を使います。

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

```d:
class MyResource
{
    ~this()
    {
        import core.memory : GC;
        import std.stdio : writeln;

        if (GC.inFinalizer)
            writeln("by GC");
        else
            writeln("by manual");
    }
}

void main()
{
    auto temp = new MyResource;
    temp.destroy(); // by manual

    {
        scope s = new MyResource;
    } // by manual
}
```

ちなみに「なぜこんな判断をしたいのか？」ですが、「手動の破棄のとき、できるだけ多くのリソースを破棄したいから」です。

元をたどれば、GCが行う破棄の処理は不要になったオブジェクトのデストラクタを**順不同で**呼び出します。
つまり、**デストラクタの中では、メンバー変数がGCによって破棄済みの可能性がある**ということです。
このときは、**GC管理下にある別オブジェクトを触ってはいけない**、ということになります。

対して、**手動で破棄したときはGCも動作しておらず、GCで管理されるオブジェクトも破棄して良い**ということになるため、**できることなら破棄してより多くのメモリを解放したい**、という要求があります。

これはGCとデストラクタを兼ね備える言語でよくある課題となっています。

たとえばC#では、これを解決するために `IDisposable` というインターフェースを設け、安全のために少々長いテンプレート的な書き方を推奨しています。小難しいことをするとき、果たしてうまく実装できているのか？といつも悩まされる部分ではあります。

- C#における `IDisposable` の実装例
    - https://docs.microsoft.com/ja-jp/dotnet/api/system.idisposable.dispose?view=netframework-4.8#-

Dではあくまでも「破棄の処理はデストラクタに集約」し、その中で適切なロジックが書けるようにしよう、という方向性になった、と言えると思います。


#### おまけ
この `GC.inFinalizer` ですが、デストラクタ以外に**メソッドの事前条件に書く**という使い方があります。

これによって「デストラクタで触れないGC管理下にあるオブジェクトを使っている」ということが表明でき、デストラクタで使ってGCから呼び出すとプログラムが未定義動作を起こさないよう即座にクラッシュすることになります。（ただしデバッグモードに限ります）

※実際には `InvalidMemoryOperationError` というエラーが発生するので、`catch (Throwable)` などすれば捕捉可能です

```d:
class MyClass
{
    Object obj;

    void hoge()
    in (!GC.inFinalizer) // 「ファイナライザの中ではない」という条件を利用者に課す
    {
        // objを使って何かする
    }
}
```

### `-unittest` 指定時の動作を単体テストのみ行うように変更

DMDには単体テストを行うようにするために `-unittest` というフラグを指定します。

今回、このフラグをライブラリではなく実行可能ファイルを生成するプロジェクトに対して指定したとき、**main関数の処理は行わず**、単体テストのみ行うようになる、という変更です。

実行ファイルにはmain関数の処理も含まれていますので、生成されるファイルには大差ありません。
実行時に `--DRT-testmode=run-main` という引数をつけることでmain関数も実行するようになります。（元の動作に戻せます）

#### `--DRT-testmode` について

詳細は `core.runtime` のドキュメントに記載があります。

- https://dlang.org/phobos/core_runtime.html#.runModuleUnitTests

`--DRT-testmode` に指定できる値は `run-main`, `test-or-main`, `test-only` の3つがあり、それぞれ以下の動作になります。

- `run-main`
    - テストとmain関数の両方を実行
- `test-or-main`
    - テストがあればテストのみ、なければmain関数を実行
- `test-only`
    - テストのみを実行

今回から `test-only` が既定値となった、ということになります。

## ライブラリ変更点

### `std.json` に値取得のための `get(T)` が追加

`std.json` に便利関数が追加されました。

元々文字列なら `value.str` 、数値なら `value.integer` や `value.floating` と使い分けていたのが、 `get` 1つにざっくり型指定で取り出せるようになるので楽になります。
（`asdf` などより高速で使いやすいライブラリを使うケースも多いと思いますが、、）

使ってみると以下のような感じです。

```d:
import std.json;

string s = `{ "a": 123 }`;
auto json = parseJSON(s);

assert(json["a"].get!double == 123.0);
```


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

今回も新たに非推奨となる機能はなく、以前から非推奨だった機能が4つ廃止されます。

- `std.array.Appender` の `toString` が廃止
- `std.functional` の `binaryReverseArgs` が廃止
- `std.bitmanip.BitArray` の `toString` が廃止
- `std.experimental.all` が廃止

なお、`binaryReverseArgs` は `reverseArgs` で代替でき、 `std.experimental.all` は `import std;` で代替ができます。

### `toString` の廃止2種について

破棄された関数は、いずれも以下のように`isCallable` なオブジェクトを受け付ける関数です。

```d:廃止されたAppenderのtoString
deprecated("To be removed after 2.089. Please use the output range overload.")
void toString(Writer)(scope Writer w)
if (isCallable!Writer)
{
    import std.format : formattedWrite;
    w.formattedWrite(typeof(this).stringof ~ "(%s)", data);
}
```

今後は、`isCallable` を包括したより広い概念である `OutputRange` を受け付けるオーバーロードがあるので、そちらを使えばOKです。

ただし `FormatSpec!char` という引数が追加で必要になりますので、少しだけ書き方の注意があります。
構築用の `singleSpec` という関数を使うと以下のように書き換えれば同じ結果が得られます。

```d:
import std.stdio : writeln;
import std.array : appender;
import std.format : singleSpec;

auto src = appender([0, 1, 2, 3, 4]);
auto dst = appender!string;
auto spec = singleSpec("%s");
src.toString(dst, spec);

writeln(dst.data); // Appender!(int[])([0, 1, 2, 3, 4])
```


# まとめ
前回に引き続き、今回も非推奨機能は追加されませんでした。

比較的機能強化や改善も少なく小規模なリリースとなった印象ですが、実は修正された不具合のほうに注目すると前回の2.089.0を上回っています。（前回は50、今回は70）

昨年から所有権や安全性に関する取り組みが行われていることもあり、コア開発者のパワーはそちらにも向けられているのですが、その分貢献者が増えたりして勢い自体は伸びているようです。

次のリリースは3月の予定となりますが、引き続き更なる強化が行われることを期待したいと思います！