LoginSignup
12
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-05-05

はじめに

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

最近はメモリ安全性への取り組みが活発で、利便性強化以外にも警告強化や「所有権と借用」のプロトタイプ実装が取り込まれるなど安全性への配慮が目玉となっています。

所有権に関してはまだまだ不具合があるとChangeLogにも書き残されていますが、実際にオンラインエディタでも試せるようになっているので、雰囲気は十分感じることができるのではないかと思います。(絶賛修正中なので nightly で試すのもおすすめです)

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

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

変更点目次

コンパイラ変更点

  • コマンドライン引数である -revert=import-transition=checkimports が削除されました
  • マングリングのため、C++のGNU ABIタグがサポートされました
  • extern(D) がついていないモジュールコンストラクタとモジュールデストラクタが非推奨になりました
  • DIP25が既定で有効になり、違反した場合は警告が発生するようになります
  • ポインタに対する所有権と借用のプロトタイプが追加されました
  • ストレージクラスの inscope const とする -preview=in が追加されました
  • printfscanf のフォーマットと引数を検証するようになりました
  • 環境変数の SOURCE_DATE_EPOCH をサポートするようになりました

改善点

  • Bugzilla 20470: AliasSeq のタプルを介したアクセスで this が考慮されない点を改善
  • Bugzilla 20609: @disabledeprecated の関数が候補に表示されてしまう点を改善
  • Bugzilla 20625: 関数リテラルの呼び出しで引数不一致のときのメッセージが一部不適切な点を改善
  • Bugzilla 20636: asm ブロックにて RDSEED 命令をサポート
  • Bugzilla 20734: scope 引数に配列リテラルを指定したときはスタック確保になるよう改善

ランタイム変更点

  • C#やJavaにある isAssignableFrom のように使える TypeInfo_Class/TypeInfo_Interface.isBaseOf が追加されました
  • core.memory.pageSizeminimumPageSize が追加されました

改善点

  • Bugzilla 19924: core.bitop.bswap(ulong) がBetterCモードで動作するように改善
  • Bugzilla 20711: object.updateupdate コールバックで更新値を返すためのコピーが不要な形式に対応
  • Bugzilla 20741: 配列の dup, idup 、組み込みの連想配列の keys, values で型がPostblitであるかどうかを確認してコードを最適化するよう改善

ライブラリ変更点

  • std.datetime.dataDate.isoWeekYearDate.fromISOWeek が追加されました
  • std.xml が非推奨になりました
  • std.digest.digest に定義された非推奨なエイリアスが削除されました

改善点

  • Bugzilla 20723: x86/x86-64 でarc4randomでないとき、std.random.unpredictableSeedRDRAND を使用するか試すようになります
  • Bugzilla 20732: swappure @nogc nothrow を持たないコピーコンストラクタを持つ型をサポートするようになります

DUB変更点

  • 隠しディレクトリは無視されるようになりました
  • dub lint--report-file 引数をサポートするようになりました

注目トピック

言語・コンパイラ変更点

強化:環境変数の SOURCE_DATE_EPOCH をサポートするようになりました

環境変数の SOURCE_DATE_EPOCH にUNIXタイムスタンプの値を設定すると、それが __DATE____TIME__ の値として使われるようになりました。

任意の時間を偽装してコンパイルができるということで便利に使えそうですが、時間を使ってランダムにコンパイルエラーを起こしている各位には残念なお知らせということになりそうです。(残念?)

試験的:ポインタに対する所有権と借用のプロトタイプが追加されました

今回のアップデートの目玉の1つです。
いわゆる「所有権と借用(Ownership and Borrowing、通称OB)」です。

昨年7月、D言語作者のWalter Bright氏がに「段階的に所有権と借用の仕組みを追加していく」との方針を明らかにしました。
ちょくちょく「実装している」というコメントや開発状況は公開されていたのですが、今回そのプロトタイプがベータ版として正式に利用できるようになりました。

当初の表明に関しては以下のあたり参照ください。

今はまだ制限や不具合も多いとのことですが、「特定条件を満たしたときのみ有効になる」というチェック機構なので、そこまで混乱を招くことはないと思われます。

コンセプトや細かい解説をするには余白が足りないので別の記事にするとして、ここでは使い方と最もシンプルな例のみ触れて雰囲気をつかんでおきたいと思います。

使い方

所有権と借用のチェックを有効にするには、以下2つの指定が必要です。

  • コンパイラ引数として -preview=dip1021 を指定する
  • チェック対象の関数に @live 属性を付与する

動作例

というわけで、何の小細工もないシンプルにダメな「二重解放」の処理を試してみます。
オンラインエディタに貼り付けて実行できますので、興味があればお好きにifやforなど組み合わせてみてください。

app.d
import core.stdc.stdlib;

void main() @live // @live属性を付与
{
    auto p = cast(byte*) malloc(1); // 1バイト確保
    free(p);
    free(p); // 2度目のfreeは不正な処理
}
コンパイル
dmd main.d -preview=dip1021

※ 2020/05/06現在、オンラインエディタでは -preview 引数の指定は不要となっています(理由不明)

コンパイル結果
onlineapp.d(7): Error: variable onlineapp.main.p has undefined state and cannot be read
onlineapp.d(7): Error: variable onlineapp.main.p is not Owner, cannot consume its value
エラーの読み方

処理としては malloc で確保したポインタを2回 free 関数に渡し誤って開放してしまう、という典型的な Double-Free という問題です。2番目の free の呼び出しが不適切です。

これに対しエラーは2つ発生しており、それぞれ「未定義状態のポインタを読み取る」「所有権を持たないポインタの移譲」について怒られています。
ちなみに (7) というのが行数で、2番目の free 関数の呼び出し箇所ですのでバッチリですね。

どちらのエラーも、原因としては「ポインタ onlineapp.main.p が、所有権を要求する free 関数に一度渡されているので、その後のポインタの状態が undefined という未定義状態になっている」ことによって発生します。
onlineapp.main.p というのは、オンラインエディタで実行したので既定の onlineapp モジュールの main 関数の変数 p を表しています)

1つ目のエラーは、1度解放したポインタをもう1度 free 関数に渡そうとするわけですが、その時「ポインタを利用しようとする」ため、「未定義状態のポインタを読み取る」ということになってエラーが発生しています。

2つ目のエラーは、「free 関数は所有権を要求する」ので、「所有権を持たない(undefined状態の)ポインタは不適切だ」ということになってエラーが発生しています。

つまり関数の中で「ポインタの状態」というものをトラッキングしていて、使っても安全なのかどうかを随時確かめています。解析はちょっと重いらしいので、こういう関数は細かくするように心がけたいですね。

言語としての利便性を保ちつつ、こういった安全性への取り組みが追加されていくのは良いことだと思います。

変更: DIP25が既定で有効になりました

かなり地味ですが、こちらも今回のアップデートにおける目玉の1つと言える内容です。

DIP25とはなんなのかというと、「関数の ref 戻り値が破棄された値を指している可能性がある」という仕様の問題を解決するために作られた「return ref セマンティクス」というものを導入する提案です。

2015年、バージョン 2.067 の頃から実装されていてフラグを立てれば利用できたのですが、今回からこのルールが既定で有効になり、違反すると警告が出るようになりました。
これもSafe by default、既定で言語の安全性を高める運動の一環です。

言葉だけでは機能がわかりづらいので、ざっと例を見ていきます。

問題が起きる例

まず何も処理をしない、何の問題もない ref 引数と ref 戻り値を使った関数があるとします。

普段は問題のない関数
ref int identity(ref int n) {
    return n;
}

これを以下のように使うと、fun 関数の戻り値は適切でしょうか?というのが当初の問題です。

問題のある使い方
ref int fun(int n) {
    return identity(n); // 引数の参照が返ってくる?
}

void main() {
    fun(10) += 10; // 参照先はどこ?可算しても大丈夫?
}

fun 関数の引数の有効期間は、当然ですが fun 関数の処理の中だけです。
では、この fun 関数の戻り値は正常な参照でしょうか?

identity 関数がいわゆるグローバル変数を返しているのであれば何の問題もありません。しかしライブラリがこうなっていたとして、使う側としては判別可能でしょうか?謎は深まるばかり。

そこでDIP25は、この identity 関数の中の「引数の参照を return する」という状況に対して以下のような警告を発生させます。

onlineapp.d(9): Deprecation: returning n escapes a reference to parameter n, perhaps annotate with return

引数の参照を戻り値に使っているので return で修飾した方が良いぞ、という内容です。

そこで以下のように引数にreturnを付けると、出ていた警告が消えます。

return修飾した適切な関数
ref int identity(return ref int n) {
    return n;
}

これで、「戻り値と引数は同じ生存期間だ」とコンパイラに教えることができます。

すると今度は問題のある使い方をしていた fun 関数の return する箇所で同じ警告が出るわけです。

identity 関数の振舞いに関して正しい情報が得られたので、これで正しいバグ位置が特定できるようになりました。めでたしめでたし。

なお C++で同じことをするとどうなるのか、興味のある方は試してみると面白いと思います。

自動推論

とまぁ毎回ここまで考えるのは大変だ、という意見もごもっともで、いくつか楽をする手があります。

まず将来強化する/ロジックが変わる、といった可能性に対し、どうしても今決められない場合は「とりあえず return を付けておく」というのが手です。
利用する側の条件は厳しくなりますが、 「return 修飾しても実際返さない」というのは特にエラーにはならないためです。

また、テンプレート関数やラムダ式に ref 引数がある場合は、return を付けるべきか自動的に推論されるようになっています。

というわけで、特定できない場合はテンプレート化してしまうのも手です。

テンプレート化
ref int identity()(/* return */ ref int n) { // 自動推論されるため書かなくて良い
    return n;
}

これはUFCSを前提としたメソッドチェイン目的で作った関数でありがちな状況なのですが、自作されている範囲でも実は既に推論されているものがあるかもしれません。

強化: C++のGNU ABIタグがサポートされました

C++連携強化の一環で、「C++ライブラリを完全にサポートするための強化」とのことです。

本家の仕様はこちら。

私も詳しくない上に「ほとんどの人は関係しない」とのことで、サンプル抜粋にとどめます。
分かる人は分かる系の内容だと思われるので、興味のある方は触ってみてください。

extern(C++):
@gnuAbiTag("tagOnStruct")
struct MyStruct {}
@gnuAbiTag("Multiple", "Tags", "On", "Function")
MyStruct func();

強化: printfscanf の引数を検証するようになる

コンパイラが core.stdc.stdioprintfscanf の引数を検証するようになります。
「書式文字列と引数があっているか検証する」ということで、チェック内容はC99の規格に準じているとのことです。

これらの関数は入出力を扱ううえで多くの不具合が発生する場所とされているので、これも安全向上の一環です。

もちろん普通にDの関数を使えば型チェック等は十分安全なのですが、BetterCモードとかもあるのでこういった機能は望まれているようです。

例はこんな感じです。

import core.stdc.stdio;

void main()
{
    int n = 100;
    printf("%s", n);
}
結果
onlineapp.d(7): Deprecation: argument n for format specification "%s" must be char*, not int

警告のみなので、これ自体が障害になってコンパイルできなくなることはありません。
見たら気を付けたいですね。

ちなみになんでこんな特化型の処理ができるかというと、裏では pragma(printf)pragma(scanf) という pragma が追加されています。
これらを付与することで、「printf のような引数を持つ関数」、「scanf のような引数を持つ関数」に対して同様のチェックを行うことができます。

フォーマット用の書式化文字列が動的に決まる場合は対象外なので、できるだけシンプルに注意して使いたいですね。

改善: タプルを介したアクセスで一部 this が利用可能になる

これも地味なのですが、随分魔術的便利そうな改善ポイントだったので取り上げておきます。

できるようになったのは、以下のように AliasSeq を使ってフィールドを使ったエイリアスが定義できるという強化です。

import std.stdio;
import std.meta;

struct S
{
    int a, b;
    alias AliasSeq!(b, a) expandReverse;
}

void main()
{
    S obj;
    obj.expandReverse = AliasSeq!(1, 2);
    writeln(obj); // S(2, 1)
}

今までは this が考慮されずに展開されたため「this の指定が足りない」という感じのエラーになっていました。

パッと思いつく限り、Vector型のSwizzle演算などに活用の道がありそうですが、利便性向上とメタプログラミングが捗りそうな強化で大変面白そうです。
(実際Swizzle演算をやるなら opDispatch のほうが正攻法かもしれませんが)

ランタイム変更点

強化: TypeInfo_Class/TypeInfo_Interface.isBaseOf が追加されました

isBaseOf というあるクラスが特定のクラスやインターフェースを継承/実装しているかどうか、を実行時型情報からチェックする関数が追加されました。

使い方としては以下の通りです。

class ClassA {}
class ClassB : ClassA {}

auto a = new ClassA(), b = new ClassB();

assert(typeid(a).isBaseOf(typeid(a)));
assert(typeid(a).isBaseOf(typeid(b)));
assert(!typeid(b).isBaseOf(typeid(a)));

またC#やJavaでは isAssignableFrom という名前だそうですが、Dでは opAssign によって代入演算のオーバーロードができるので、あえて継承のみに絞った名前としているそうです。

強化: core.memorypageSizeminimumPageSize が追加

それぞれシステムがメモリを管理する際に使うページサイズを取得できるようになります。

違いとしては、

  • minimumPageSize はコンパイル時に利用できるプラットフォーム固有の値
  • pageSize は実行時に設定される実行環境の値

です。

ということで通常は pageSize を使うことが推奨されています。

こういった値がランタイムに組み込まれることで、より手軽にポータブルでパフォーマンスの高いメモリ管理の仕組みが作れるようになりそうですね。

ライブラリ変更点

強化: Date.isoWeekYearDate.fromISOWeek が追加されました

いずれも、日付等に関する国際規格である ISO 8601 で定義された ISO week date system という仕組みに基づくメソッドです。
主に会計用途とのことで、あまり縁のない方のほうが多いかもしれません。

Date.fromISOWeekDate 型の staticメソッドで、 要は「何年」の「何周目」の「何曜日」という情報から実際の日付を計算して Date 型のオブジェクトを取得します。

isWeekYearDate オブジェクトのプロパティで、「ISO week dateにおける年」を取得するプロパティです。

ただし第1週目の定義として、「その年の第1木曜日を含む週が1週目(引数的には0)」ということで、年末年始が通常の西暦(グレゴリオ暦)とは異なる年にカウントされることが多々あります。

たとえば、2020年の第1木曜は 1月2日 なのですが、同週の月曜を見ると 2019年12月30日 です。
結果、 Date.fromISOWeek(2020, 1, DayOfWeek.mon)2019年12月30日 ということになります。

ということであまり通常使いするようなものではないかもしれません。
おそらく便利に使える職種の方が要望されたものかと思います。

変更: unpredictableSeedRDRAND を使用するようになる

unpredictableSeed というのは乱数シード用の予測不能な値を返すもので、RDRAND というのはハードウェア乱数命令です。
よりセキュアな乱数シードが使われるということで、そういった機能を求める方含め多方面に活用されやすくなる変更かと思います。

ちなみにハードウェア乱数が使われているかどうかは、 core.cpuid モジュールの hasRdrand 関数によって判定されています。自分の環境が気になる人はこのフラグを確認してみてください。

また言語機能のほうでも asm ブロック内で RDSEED 命令を使えるようになる、という改善が行われています。
これもハードウェア乱数の一種が使える命令ですが、 RDRAND よりも予測可能性の面で有利とのことです。ただし幾分新しい命令でCPUを選ぶので、標準ライブラリとしては現状 RDRAND を使っておくのが妥当そうです。

改善: object.update のコールバックが ref 形式に対応

object.update というのは、連想配列の値を登録または更新するためのメソッドです。

k1 というキーがなければ 1 を追加、キーがあれば値に 1 を足すという処理をするとき、元々以下のように利用するものです。

int[string] aa;
aa.update("k1", { return 1; }, (int n) { return n + 1; });

この2番目の引数が ref に対応します。

aa.update("k1", { return 1; }, (ref int n) { n += 1; });

強化の動機としては戻り値をコピーするコストが高いということで、「ref にすれば戻り値は不要だろう」とのことからこうなったようです。

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

廃止された機能

今回の更新では、非推奨になったものが2つ(実際は3つ)、削除されたものが2つあります。

非推奨

  • extern(D) がついていないモジュールコンストラクタとモジュールデストラクタが非推奨になりました
  • std.xml が非推奨になりました

削除

  • コマンドライン引数である -revert=import-transition=checkimports が削除されました
  • std.digest.digest に定義された非推奨なエイリアスが削除されました

extern(D) ではないモジュールコンストラクタ/デストラクタが非推奨

これは static this() {}shared static ~this() {} といったモジュールコンストラクタ/デストラクタを extern(C) などと組み合わせて使うのは非推奨とする、というものです。

元々 extern(C) というのはマングリング規則を変えるような作用を持ちますので、モジュールコンストラクタの呼び出しの際にマングリングが衝突する可能性がある(ただし可能性は非常に低い)、という理由です。

どうしても extern(C) してるブロックで定義したいなと思ったら、個別に extern(D) static this() {} とすれば良いので、警告を見たら個々に追加してやれば良いかと思われます。

std.xmlが非推奨

標準ライブラリからXML操作に関するモジュールが非推奨となりました。
理由としては、設計的に古く最新の言語機能やデザインにそぐわないこと、代替ライブラリがそれなりに出てくるようになったこと、の大きく2点のようです。

公式のChangeLogには dxml というパッケージがおすすめされています。

2020年5月現在、候補となるライブラリは dxml を含め3つほどありますのでざっと紹介しておきます。

ざっくり機能を比較すると以下の通りです。

dxml rapidxml arsd-official:dom
特徴 XML1.0の仕様テストをクリア。Range対応の解析器を持つ C++のrapidxmlを再実装したもの 大規模サブパッケージ集の1つ。HTMLやRSSの解析でも利用されている。
名前空間の対応
XPath
速度
おすすめ度

3つ挙げてみましたが、この中ではやはり仕様テストまで設けている dxml が安心して利用できるかな、というところです。

ただ、 rapidxml はその名の通り「高速」が特徴で、C++の本家公式が「可能な限り最速を目指す」と公言するほどパフォーマンス重視のライブラリです。
再実装したのはD言語のWebフレームワークを作っている huntlabs というグループなのですが、元々持っていた hunt-xml より後にこのライブラリを公開しているので、かなり速度的なメリットがあってのことだと思われます。

調べて意外だったのは、かなり大規模なパッケージ集である arsd-official の中に dom というサブモジュールがあり、これがRSSの解析に使われていたことです。さすが何でもあり。
目立った機能不足は名前空間などの対応があるようですが、基本的に外部データに対して使って実績があるのは強みであり、特化型として結構使える可能性は高いと思われます。

実は他にも個人製作ながら pham_xml という、機能的にはXPath含め最も豊富で、標準ライブラリと比較してもメモリ消費と速度ともに3倍以上の効率、とのことから期待大のパッケージがあります。
しかしモジュール構成などがdubパッケージの標準形式にあっておらず、現時点では使おうとするとビルドエラーが出るので、組み込むのであればForkして使うなどの工夫が必要そうです。

-revert=import-transition=checkimports が削除

ざっくり言ってしまうと、今はもう特に何もしていないフラグなので消した、というものです。
細かい情報は見つけられなかったのですが、特に影響は無さそうです。

元々 -revert=XXX-transition=XXX というのはコンパイラフラグの実装パターンの1つで、「既に実装された機能だがどうしてもオフにしたい」「移行段階にある機能の動作を切り替えたい」というときに使うものです。

今はもう広く浸透したものであれば、メンテナンス等考えると順次削っていくのが健全かと思います。

バージョンアップで困ったときにこのあたりのフラグを思い出すと何かしら役に立つかもしれませんね。

std.digest.digest の非推奨エイリアスが削除

消えたエイリアスは以下の通りです。
たくさんありますが、いずれも std.digest に同名の定義がありますので、そちらをimportすればコード変更は不要なはずです。

ちなみに std.digest.digest 自体も非推奨となり、 2.101 で削除予定となっています。

  • isDigest
  • DigestType
  • hasPeek
  • hasBlockSize
  • digest
  • hexDigest
  • makeDigest
  • Digest
  • Order
  • toHexString
  • asArray
  • digestLength
  • WrapperDigest
  • secureEqual
  • LetterCase

DUB変更点

強化: dub lint--report-file 引数をサポート

dub lint はコードのフォーマット等をチェックして不適切な箇所について警告等を発生させるコマンドです。

これに対し dub lint --report-file report.json と実行すると report.json ファイルに結果を出力できるようになります。

結果がJSON形式なのがポイントでしょうか。分析したりDIFF見るのに良いかもしれませんね。

まとめ

公式に掲げられた安全性強化の目標を中心に、いくつかの安全強化策の追加、加えてC++連携や利便性の強化が着々と行われるリリースになったと思います。

また、ついに所有権と借用の仕組みが入るときが近づいてきてきました。

とはいえ標準ライブラリや各種DUBパッケージが完全に対応するまでは様子見の人が大半かと思いますので、これからの強化に期待しつつ、まずは言語的に興味のある方にチャレンジしてみてもらえればと思います。

というわけで次回、プロトタイプから正式になるのか、はたまた別のトピックがあるのか、7月版を期待して待ちたいと思います!

12
1
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
12
1