D言語

【D言語】dmd v2.101.0へのアップデートで踏んだ破壊的変更

久々に既存コードが壊れちまった!

日本時間の11月16日にdmdのv2.101.0がリリースされた。

筆者は基本的に、dmdがバージョンアップされるたびに(嬉々として)アップデートしているのだが、今回かなり久しぶりに破壊的変更に見舞われた。

今回起こった事象の中には、筆者の勘違いや環境固有のものが含まれるかもしれないし、そもそも筆者のプログラムミスが明るみに出ただけのものもあるが、一応まとめておく。

破壊的変更一覧

今回踏んだ破壊的変更(と思われるもの)は以下の3件。

CおよびWindowsリンケージでのin引数の無効化

これは、コンパイル引数に-preview=inが設定されていることが前提となる。

まず説明しておくと、-preview=inとは、func(in T t)という引数をfunc(scope const ref T t)という風に(状況に応じて)変換してくれる機能だ。

readonlyな引数にしたい場合に、scope const refとか長ったらしい記述をせずに単にinと書けば済むという、タイピングや見た目の面でのメリットがまずひとつ。

そしてもうひとつは、構造体の場合、サイズに応じて値コピーにするか参照渡し(ref)にするかを自動的に判断して最適化を施してくれるという点。これがとてもナイスなのだ。

話を戻す。この-preview=inによって、引数のinscope const refに変換された場合に、それがD言語のインターフェースなら問題ないのだが、CやWindowsのABIにはscope refに相当するものがないのでマズイことになる。

そこで、v2.101.0からはextern(C)およびextern(Windows)のリンケージを持つインターフェースにinを使用するとコンパイルエラーになるよう変更が入ったわけだ。

この変更自体は至極もっともで理にかなったものなのだが、これによって、Phobosに使われていたextern付きのin引数が軒並みコンパイルエラーを吐くようになってしまったのだ。

具体的には、std.internal.windows.advapi32std.stdioが影響を受けたモジュールである。

一応D言語コミュニティーもこの問題に気づいていたらしく、inscope const書き換える対応を行っていたが、どうやらこれはmasterにしか適用されておらず、v2.101.0のリリースには適用されていないようだ。

仕方がないので、ライブラリ側の問題となっているコードを自力で書き換えて何とかしのいだが、この対応で問題なかっただろうか? 少々不安である。

-preview=inは、まあたしかに文字通りプレビュー機能とはいえ、この破壊的変更は痛かった…

dubが生成するビルドフォルダ名のハッシュ値の伸長

次に悩まされた問題が、謎のリンカエラーである。

dmdをv2.101.0にアップデートしてから、リンカエラーLNK1181が出てビルドが失敗するようになってしまった。

調べたところ、LNK1181はシンプルに「ファイルが見つからない」場合に出るエラーなのだそうだが、指摘されたファイルはどう見ても存在している。

コンパイルされたオブジェクトファイルが前のバージョンとどう違うのかよく比較してみると、なんとdubが.dub/build配下に生成するフォルダ名のハッシュ値が倍の長さになっているではないか!

// ひとつ前のv2.100.2時点でのフォルダ名

application-debug-windows-x86_64-dmd_v2.100.2-dirty-7E555D505519518954CA52E315779AD4
// v2.101.0でのフォルダ名

application-debug-windows-x86_64-dmd_v2.101.0-dirty-28CFF3979F5ED822458DBC18CDFF58A0C4E26571924F35A3852F0AC65799B5ED

筆者のプロジェクトは若干深いディレクトリ構成になっていたので、この32文字の伸長によって、Windowsの「パス長260文字制限」に引っかかりオブジェクトファイルが検索不能になっていたわけだ。

ディレクトリ構成を見直してパスが短くなるようにしたら無事ビルドできるようになった。

割とすぐに原因に気づけたのでよかったが、ハマっていたらいったいどれだけの時間を溶かしただろうか…

関数属性のscopereturnの順序チェックが厳格化?

これは根本部分は筆者のミス。

return scope修飾されたインスタンスのメソッドからthisや参照メンバを返せるようにするには、メソッドにreturn scopeを付与しなければならないのだが、これを誤ってscope returnの順に書いてしまっていた。

// 正しい

T get() return scope => this;
// この場合は不適(`return ref` + `scope` という解釈になってしまう)

T get() scope return => this;

ささいな順番の違いのようだが、意味が全く異なってしまう。詳しくはここを参照していただきたい。

でも、v2.100.2までは普通にコンパイル通ってたんだよなあ… なんでまた急に…

感想

D言語の破壊的変更に咽び泣くなんて、v1とか、v2に入ったばかりの頃の昔話だと思っていたが、最近でも踏むときは踏むもんだなあ。

ところで、D言語フォーラム等でもこういう情報って見かけないのだが、筆者のおま環だったり、勘違いだったりするんだろうか?

そうだったら恥ずかしい記事になっちゃう…