はじめに
D言語の公式コンパイラである dmd が更新され、最新バージョンである 2.092.0
が 2020/05/10 にリリースされました。
最近はメモリ安全性への取り組みが活発で、利便性強化以外にも警告強化や「所有権と借用」のプロトタイプ実装が取り込まれるなど安全性への配慮が目玉となっています。
所有権に関してはまだまだ不具合があるとChangeLogにも書き残されていますが、実際にオンラインエディタでも試せるようになっているので、雰囲気は十分感じることができるのではないかと思います。(絶賛修正中なので nightly
で試すのもおすすめです)
より細かい内容は下記リンクからChangeLogをご覧ください。
- ChangeLog
また、前回3月ののリリースまとめは以下になります。
- D言語の更新まとめ 2020年3月版(dmd 2.091.0)
変更点目次
コンパイラ変更点
- コマンドライン引数である
-revert=import
と-transition=checkimports
が削除されました - マングリングのため、C++のGNU ABIタグがサポートされました
-
extern(D)
がついていないモジュールコンストラクタとモジュールデストラクタが非推奨になりました - DIP25が既定で有効になり、違反した場合は警告が発生するようになります
- ポインタに対する所有権と借用のプロトタイプが追加されました
- ストレージクラスの
in
をscope const
とする-preview=in
が追加されました -
printf
とscanf
のフォーマットと引数を検証するようになりました - 環境変数の
SOURCE_DATE_EPOCH
をサポートするようになりました
改善点
- Bugzilla 20470:
AliasSeq
のタプルを介したアクセスでthis
が考慮されない点を改善 - Bugzilla 20609:
@disable
やdeprecated
の関数が候補に表示されてしまう点を改善 - Bugzilla 20625: 関数リテラルの呼び出しで引数不一致のときのメッセージが一部不適切な点を改善
- Bugzilla 20636:
asm
ブロックにてRDSEED
命令をサポート - Bugzilla 20734:
scope
引数に配列リテラルを指定したときはスタック確保になるよう改善
ランタイム変更点
- C#やJavaにある
isAssignableFrom
のように使えるTypeInfo_Class/TypeInfo_Interface.isBaseOf
が追加されました -
core.memory.pageSize
とminimumPageSize
が追加されました
改善点
- Bugzilla 19924:
core.bitop.bswap(ulong)
がBetterCモードで動作するように改善 - Bugzilla 20711:
object.update
のupdate
コールバックで更新値を返すためのコピーが不要な形式に対応 - Bugzilla 20741: 配列の
dup
,idup
、組み込みの連想配列のkeys
,values
で型がPostblitであるかどうかを確認してコードを最適化するよう改善
ライブラリ変更点
-
std.datetime.data
にDate.isoWeekYear
とDate.fromISOWeek
が追加されました -
std.xml
が非推奨になりました -
std.digest.digest
に定義された非推奨なエイリアスが削除されました
改善点
- Bugzilla 20723: x86/x86-64 でarc4randomでないとき、
std.random.unpredictableSeed
がRDRAND
を使用するか試すようになります - Bugzilla 20732:
swap
がpure
@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氏がに「段階的に所有権と借用の仕組みを追加していく」との方針を明らかにしました。
ちょくちょく「実装している」というコメントや開発状況は公開されていたのですが、今回そのプロトタイプがベータ版として正式に利用できるようになりました。
当初の表明に関しては以下のあたり参照ください。
- https://blog.kotet.jp/2019/07/ownership-and-borrowing-in-d/
- https://qiita.com/lempiji/items/e114dcdfdeeed2e9064c
今はまだ制限や不具合も多いとのことですが、「特定条件を満たしたときのみ有効になる」というチェック機構なので、そこまで混乱を招くことはないと思われます。
コンセプトや細かい解説をするには余白が足りないので別の記事にするとして、ここでは使い方と最もシンプルな例のみ触れて雰囲気をつかんでおきたいと思います。
使い方
所有権と借用のチェックを有効にするには、以下2つの指定が必要です。
- コンパイラ引数として
-preview=dip1021
を指定する - チェック対象の関数に
@live
属性を付与する
動作例
というわけで、何の小細工もないシンプルにダメな「二重解放」の処理を試してみます。
オンラインエディタに貼り付けて実行できますので、興味があればお好きにifやforなど組み合わせてみてください。
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を付けると、出ていた警告が消えます。
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();
強化: printf
と scanf
の引数を検証するようになる
コンパイラが core.stdc.stdio
の printf
と scanf
の引数を検証するようになります。
「書式文字列と引数があっているか検証する」ということで、チェック内容は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.memory
に pageSize
と minimumPageSize
が追加
それぞれシステムがメモリを管理する際に使うページサイズを取得できるようになります。
違いとしては、
-
minimumPageSize
はコンパイル時に利用できるプラットフォーム固有の値 -
pageSize
は実行時に設定される実行環境の値
です。
ということで通常は pageSize
を使うことが推奨されています。
こういった値がランタイムに組み込まれることで、より手軽にポータブルでパフォーマンスの高いメモリ管理の仕組みが作れるようになりそうですね。
ライブラリ変更点
強化: Date.isoWeekYear
と Date.fromISOWeek
が追加されました
いずれも、日付等に関する国際規格である ISO 8601
で定義された ISO week date system
という仕組みに基づくメソッドです。
主に会計用途とのことで、あまり縁のない方のほうが多いかもしれません。
- ISO week date
Date.fromISOWeek
は Date
型の staticメソッドで、 要は「何年」の「何周目」の「何曜日」という情報から実際の日付を計算して Date
型のオブジェクトを取得します。
isWeekYear
は Date
オブジェクトのプロパティで、「ISO week dateにおける年」を取得するプロパティです。
ただし第1週目の定義として、「その年の第1木曜日を含む週が1週目(引数的には0)」ということで、年末年始が通常の西暦(グレゴリオ暦)とは異なる年にカウントされることが多々あります。
たとえば、2020年の第1木曜は 1月2日
なのですが、同週の月曜を見ると 2019年12月30日
です。
結果、 Date.fromISOWeek(2020, 1, DayOfWeek.mon)
は 2019年12月30日
ということになります。
ということであまり通常使いするようなものではないかもしれません。
おそらく便利に使える職種の方が要望されたものかと思います。
変更: unpredictableSeed
が RDRAND
を使用するようになる
unpredictableSeed
というのは乱数シード用の予測不能な値を返すもので、RDRAND
というのはハードウェア乱数命令です。
よりセキュアな乱数シードが使われるということで、そういった機能を求める方含め多方面に活用されやすくなる変更かと思います。
ちなみにハードウェア乱数が使われているかどうかは、 core.cpuid
モジュールの hasRdrand
関数によって判定されています。自分の環境が気になる人はこのフラグを確認してみてください。
また言語機能のほうでも asm
ブロック内で RDSEED
命令を使えるようになる、という改善が行われています。
これもハードウェア乱数の一種が使える命令ですが、 RDRAND
よりも予測可能性の面で有利とのことです。ただし幾分新しい命令でCPUを選ぶので、標準ライブラリとしては現状 RDRAND
を使っておくのが妥当そうです。
- The Difference Between RDRAND and RDSEED
改善: 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
- https://code.dlang.org/packages/arsd-official%3Adom
- 主にHTML用なので厳密にはXML用途ではありませんが、目的によっては利用可能なので取り上げています
ざっくり機能を比較すると以下の通りです。
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月版を期待して待ちたいと思います!