LoginSignup
14
4

More than 3 years have passed since last update.

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

Posted at

はじめに

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

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

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

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

変更点目次

コンパイラ変更点

  • lazy 引数から delegate が得られるようになりました。

改善点

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

ランタイム変更点

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

改善点

  • Bugzilla 17563: gc_inFinalizerpublic に変更しました。

ライブラリ変更点

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

改善点

  • Bugzilla 20198: floatdouble に対して、std.mathnextUp, nextDown, nextafter がCTFEで動作するように改善
  • Bugzilla 20288: std.formatNaN のときの動作を改善
  • Bugzilla 20425: ProxyopCmp がオーバーロードされた opCmp でコンパイルに失敗する問題を改善
  • Bugzilla 20439: memoizevoid を返す opAssign で失敗する問題を改善

注目トピック

言語・コンパイラ変更点

強化: lazy な引数が delegate に変換可能になりました

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

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

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

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

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

ランタイム変更点

強化: core.memoryGC.inFinalizer が追加

GCの状態を取得する関数として、「ファイナライザの実行中かどうか」を取得できるようになりました。
「ファイナライザ」というのは普段あまり聞きませんが、要するに「GCが使われないオブジェクトを破棄するときに行う処理」のことです。

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

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

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

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

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 というインターフェースを設け、安全のために少々長いテンプレート的な書き方を推奨しています。小難しいことをするとき、果たしてうまく実装できているのか?といつも悩まされる部分ではあります。

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

おまけ

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

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

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

class MyClass
{
    Object obj;

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

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

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

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

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

--DRT-testmode について

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

--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.integervalue.floating と使い分けていたのが、 get 1つにざっくり型指定で取り出せるようになるので楽になります。
asdf などより高速で使いやすいライブラリを使うケースも多いと思いますが、、)

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

import std.json;

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

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

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

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

  • std.array.AppendertoString が廃止
  • std.functionalbinaryReverseArgs が廃止
  • std.bitmanip.BitArraytoString が廃止
  • std.experimental.all が廃止

なお、binaryReverseArgsreverseArgs で代替でき、 std.experimental.allimport std; で代替ができます。

toString の廃止2種について

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

廃止された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 という関数を使うと以下のように書き換えれば同じ結果が得られます。

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月の予定となりますが、引き続き更なる強化が行われることを期待したいと思います!

14
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
4