はじめに
D言語の公式コンパイラである dmd が更新され、最新バージョンである 2.090.0
が 2020/01/05 にリリースされました。
強化の規模は比較的小さめですが、トピックになるものがあるので触れておきたいと思います。
より細かい内容は下記リンクからChangeLogをご覧ください。
- ChangeLog
また、前回11月ののリリースまとめは以下になります。
- D言語の更新まとめ 2019年11月版(dmd 2.089.0)
変更点目次
コンパイラ変更点
-
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
だと思って &
をつける感じです。
便利に使っていきたいですね。
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
ストレージクラスを付けた変数を使います。
使ってみると以下のような感じになります。
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
の実装例
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.integer
や value.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.Appender
のtoString
が廃止 -
std.functional
のbinaryReverseArgs
が廃止 -
std.bitmanip.BitArray
のtoString
が廃止 -
std.experimental.all
が廃止
なお、binaryReverseArgs
は reverseArgs
で代替でき、 std.experimental.all
は import std;
で代替ができます。
toString
の廃止2種について
破棄された関数は、いずれも以下のようにisCallable
なオブジェクトを受け付ける関数です。
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月の予定となりますが、引き続き更なる強化が行われることを期待したいと思います!