はじめに
D言語の公式コンパイラである dmd が更新され、最新バージョンである 2.104.0
が 2022/06/02 にリリースされました。
今回は前回からほぼ2ヶ月でのリリースで、ツール類も含むかなり網羅的な強化になっているため一通りの内容ご紹介していきます。
より細かい内容は下記リンクからChangeLogをご覧ください。
- ChangeLog
また、前回である2月版のリリースまとめは以下になります。
- D言語の更新まとめ 2023年4月版(dmd 2.103.0)
変更点目次
コンパイラ変更点
- 属性推論が失敗した時のエラーメッセージが改善されました。
-
;
を空文として使用することはエラーになりました。 -
in
パラメータをextern(D)
/extern(C++)
以外の関数で使用することは非推奨になりました。 - パラメータに対する
in ref
は-preview=in
を使用するため非推奨になりました。 - 修飾されたオブジェクトをスローすることは非推奨となりました。
- ユーザー定義属性(UDA)は、テンプレート引数のように解析されるようになります。
改善点
- Bugzilla 13577:
immutable
なforeach
ループの拒否についてのエラーメッセージをより具体的にする - Bugzilla 17374: 推測された属性のエラーメッセージを改善
- Bugzilla 20268: 匿名関数のパラメーターの不一致エラーにパラメーターが含まれていない
- Bugzilla 22559: ImportC: gnuケースの範囲をサポート
- Bugzilla 23401: ImportC: Cプリプロセッサを選択するための
-cpp=filename
スイッチを追加 - Bugzilla 23862:
with
文はenum
型の式を受け入れるべき
ランタイム変更点
変更点
今回はありませんでした。
改善点
- Bugzilla 11989:
TickDuration
を非推奨にする
ライブラリ変更点
-
std.algorithm.comparison.clamp
のstatic assert
メッセージが改善されました。 -
std.typecons.Rebindable
がすべてのタイプをサポートするようになりました。
改善点
- Bugzilla 6106:
replace
関数の変更の追跡を維持する - Bugzilla 14478:
isInputRange
は非コピー可能な要素の範囲を許可するべき - Bugzilla 23769: コンパイルされない lambda において、Lambda は単項述語ではない
ツール変更点
-
rdmd
で-shared
がサポートされました。
DUB 変更点
- 新たなプロパティとして
cSourcePaths
とcImportPaths
が SDL/JSON に追加されました。 - パッケージの内部的な保管方法が変更されました。
注目トピック
言語・コンパイラ変更点
属性推論が失敗した時のエラーメッセージが改善されました。
「ある関数が内部で呼び出す関数属性を推論できず、推論エラーが起きたとき」のメッセージを改善するという変更です。
対象となる属性は @nogc
, nothrow
, pure
の3つですが、推論エラーになる理由をかなり深くまで調べて報告してくれるようになるため、かなり有効な改善点と言えます。
たとえば void main() @nogc
-> void fun()
-> void funImpl()
という呼び出し関係を持つ以下のようなソースがあるとき、
今までは「main
は fun
が呼び出せない。@nogc
ではないから」というエラーが起きていました。
今後はこれに加えて「fun
を @nogc
だと推論できなかったのは funImpl
に配列リテラルがあり、GC割り当てであるから」という内容を含んだメッセージになります。
void main() @nogc
{
fun();
}
auto fun()
{
funImpl();
}
auto funImpl()
{
int[] a = [1, 2, 3]; // GC割り当てのため @nogc にならない
}
今までのエラーメッセージ
app.d(4): Error: @nogc function D main cannot call non-@nogc function app.fun
今後のエラーメッセージ
app.d(4): Error: @nogc function D main cannot call non-@nogc function app.fun
app.d(7): which calls app.funImpl
app.d(14): which wasn't inferred @nogc because of:
app.d(14): array literal in @nogc function app.funImpl may cause a GC allocation
これは元々 @safe
属性に対して dmd 2.101 から実装されていた機能を @nogc
, nothrow
, pure
の3つの属性にも拡張したもの、とのことです。
個人的には、戻り値に auto
と書いたことで属性が推論されることを初めて知ったのが地味に衝撃でした。(今までもそうだったのか…?)
またこの強化の嬉しい点は、ChatGPTなんかのAIモデルにエラーメッセージを投げたとき、適切に修正してくれたり手数が減りそうだという点です。
今までだと fun
に1回属性を付けてコンパイル、次にダメな funImpl
に属性付けてコンパイル、その結果GC割り当ての場所が明らかになる、という流れなので、今回の例では2手削減できます。
生産性の向上に寄与してくれそうです。
ユーザー定義属性(UDA)は、テンプレート引数のように解析されるようになります。
ユーザー定義属性(UDA)に関する文法強化です。
元々UDAには型を指定することができていたのですが、基本型を使うには以下のように1度 alias
を取る必要がある、といった手間がありました。
alias Tint = int;
@Tint void f();
簡単な文字列リテラルは @(...)
という指定によって直接指定できていたのですが、これを強化し、テンプレート引数のように省略できるようになります。
たとえば foo!("arg")
が foo!"arg"
と書けるように、以下のような文法が認められるようになります。
@int void f();
@"my test" unittest
{
}
強化の詳細としては、テンプレート引数の解析部がそのまま @
の後に現れることができるようになった、ということのようです。
ここからどういった用途があるのかまだ把握しきれていませんが、やはり単体テスト(unittest
)に名前を付けるなどの用途で知られた @("...")
が @"..."
と書けるようになるのが一番影響があるかもしれません。
見かけても驚くことの無いように心の準備をしておくと良さそうです。
ライブラリ変更点
std.algorithm.comparison.clamp
の static assert
メッセージが改善されました。
clamp
関数は、ある値を下限と上限の間に収めるようにする関数です。(min
とmax
を使って書いているケースも多いかもしれません)
ドキュメント: https://dlang.org/library/std/algorithm/comparison/clamp.html
これまで clamp
関数は、引数に対して渡された型が使えるかどうかチェックするためにテンプレート制約を使っていました。
この手法は確実に、マッチするものがない場合に原因を突き止めるのが非常に面倒という課題がありました。
clamp
関数の定義は、テンプレート制約がオーバーロード解決に用いられていないため、これを等価な static assert
に書き換えることができます。
今回これによって十分に読みやすいエラーメッセージが表示できるようになった、という強化です。
今までも度々類似の改善は行われており、今後もエラーメッセージの改善は継続されると思います。
std.typecons.Rebindable
がすべてのタイプをサポートするようになりました。
Rebindable
型は、再束縛が可能な不変値を表すための型です。
ドキュメント: https://dlang.org/library/std/typecons/rebindable.html
今回の強化により、Rebindable
は immutable
な構造体を含む、あらゆる型の値の保持と再バインドに使用できるようになりました。
元々クラスやインターフェース、配列などに対して以下のように用いているものでした。
今後は、構造体など様々な型に使えるようになったということです。
class Widget {
int x;
int y() const { return x; }
}
auto a = Rebindable!(const Widget)(new Widget);
a.y(); // constの呼び出しや取得系はOK
a.x = 5; // error! constのため書き換え不可
a = new Widget; // 再代入はOK
with
文は enum
型の式を受け入れるべき
ちょっとした内容ですが改善です。
D言語には メンバーアクセスを省略する際の記法を省略可能にする with
文があります。
この機能を使って、switch
と with
文を組み合わせたイディオムに以下のような記法があります。
enum E { A, B }
void test(E e) {
// switch + with
final switch (e) with (E) {
case A: // Aが見つからないので with (E) を頼りに E から取ってくる
case B:
break;
}
}
これが今回の改善により、with (E)
ではなく with (e)
でも通るようになりました。
記述の意味を考えると、元々クラスのメンバーアクセスで記述を省略するための文なので、enum
の場合に値を受け付けないのはおかしい、という話から強化されているようです。
こうなると e.A
にアクセスできていることになって、それはそれで若干異常ではないかと思うところがなくもないですが、実は e.A
という方法のアクセスは元々許されています。
というわけで typeof
を使ったりして型を取らずとも動くようになりますので便利に使っていきましょう。
ツール変更点
rdmd
で -shared
がサポートされました。
rdmd
は、D言語のソースファイルを受け取って直接実行するコンパイラ同梱の公式ツールです。
rdmd app.d
で app.d
をスクリプト的に実行できます。めっちゃ便利なので使ってみてください。
さて、今回の強化により rdmd
はDMDの -shared
スイッチを理解するようになりました。
これによってなんと実行するだけではなく、DLLなどの参照ライブラリを作ることができるようになります。プラットフォームによって、.dll
、.so
、.dylib
のいずれかになります。
たとえばWindows環境であれば、以下のように同じフォルダにDLL用の testlib.d
とメイン処理を書く app.d
を作って、
export int test(int a, int b) {
return a + b;
}
rdmd -shared testlib.d
で testlib.dll
を作り、
import std;
import core.runtime;
void main() {
void* p = Runtime.loadLibrary("testlib");
writeln(p); // アドレスが表示される
}
rdmd app.d
で実行するとアドレスが表示される、ということができます。
DLLを作るには細かい準備があったりしたのですが、今回の rdmd
強化によってそれが全然いらなくなりました。
実際に関数のアドレスの取って呼ぶ方法は以下を参考にしてください。
参考: https://wiki.dlang.org/Win32_DLLs_in_D
DUB変更点
新たなプロパティとして cSourcePaths
と cImportPaths
が SDL/JSON に追加されました。
cSourcePaths
は、指定されたすべてのディレクトリのCのソースファイルをコンパイラに渡すスイッチです。
元々D言語のコンパイラは ImportC というC言語のソースコードをコンパイルする機能を持っているので、ファイルとして含めてやればまとめてコンパイルできます。
この機能により、Cのソースを持つ別のプロジェクトをDに移植する際、dub
で少しスイッチを指定するだけでソースコードが取り込まれるようになります。
例として、以下のようなディレクトリ構造のプロジェクトを作るとします。
project
c
testlib.c // 既存のC言語ソースファイル
dproj
source
app.d
dub.sdl
この場合、dub.sdlに一行以下のように書き足すと .c
のソースが取り込まれます。
cSourcePaths "../c"
これだけだとC言語の関数をエディタで使うときにエラーが出て面倒なので、補完が使えるようにセットで以下のように書くと良いです。
dflags "-Hd=headers"
importPaths "headers"
これを書いておくと、dub.sdl
と同じ階層に headers
ディレクトリが作られ、そこに型定義だけ書いてある .di
ファイルが作られ、Dのソースを書くときに import
で型定義が利用できるようになります。
cImportPaths
は、Cのソースファイルを検索するパスとのことです(今のところ cSourcePaths
との違いが分からない)。
こちらは今後調整されるかもしれない、とのことで今回は省略させていただきます。
パッケージの内部的な保管方法が変更されました。
dub
で参照しているパッケージのキャッシュやビルドキャッシュの保存先が変更されます。
以前のバージョンの dub
では、パッケージは以下の形式で保存されていました。
変更前: $CACHE_PATH/$PACKAGE_NAME-$PACKAGE_VERSION/$PACKAGE_NAME/
変更後: $CACHE_PATH/$PACKAGE_NAME/$PACKAGE_VERSION/$PACKAGE_NAME/
少し補足しておくと、Windowsでは $CACHE_PATH
= %LOCALAPPDATA%\dub\cache
です。
これはキャッシュですが、横に \dub\packages
というパッケージ自体を管理しているディレクトリもあり、同様の変更を受けます。
変更内容は、元々パッケージとバージョンを統合したディレクトリ名だったところ、分離して階層を1つ切って整理した、というだけです。
これによってパッケージを素早く一覧できるようになり、同時に頻繁に更新される可能性のあるパッケージがリストに何度も現れなくなります。
ただし、cache
や packages
に昔のファイルがそのまま残ることはあるため、docker
などのビルドキャッシュがあれば一度綺麗にした方が良いかと思います。
このあたりは、将来的にはパッケージ単位のGCも視野に入っているようです。
パスからバージョンを推測するなどが可能になることで、dub
の内部処理を高速化することにも役立つだろうとのことです。
非推奨または廃止される機能
今回は、非推奨が4件、廃止が1件で計5件の機能が非推奨および廃止となります。
余り影響のないものが多いですが、一部わかりづらいものもありますので一つ一つ解説していきます。
また今後の予定などは以下のページにまとまっていますので、こちらも参考としてみてください。
非推奨
-
in
パラメータをextern(D)
/extern(C++)
以外の関数で使用することは非推奨になりました。 - パラメータに対する
in ref
は-preview=in
を使用するため非推奨になりました。 - 修飾されたオブジェクトをスローすることは非推奨となりました。
- Bugzilla 11989:
TickDuration
を非推奨にする
削除/エラー
-
;
を空文として使用することはエラーになりました。
非推奨 : in
パラメータを extern(D)
/extern(C++)
以外の関数で使用することは非推奨になりました。
in
パラメーターは、D言語における入力値を示す修飾子の1つで、引数の値が更新できないことを制約として課し、右辺値参照などを受け取って効率化されることが特徴です。
この非推奨は、そこまで影響は大きくなく、影響を受ける人もかなり限られるかと思います。
extern(C)
などでも in
を付与することはできたのですが、今回の更新から非推奨になります。
実際に出力されると以下のような警告になります。
main.d(11): Deprecation: using `in` parameters with `extern(C)` functions is deprecated
main.d(11): parameter `n` declared as `in` here
このような警告を見つけたら in
を取ることで対処できます。
非推奨 : パラメータに対する in ref
は -preview=in
を使用するため非推奨になりました。
in
パラメーターは、通常単なる const
と同じ意味で用いられます。
また、-preview=in
を使用している場合、関数の引数に対する in
修飾は、その引数が左辺値でも右辺値でも取れるという仕様になっています。
対して、ref
はアドレスが取れることを示すので、たとえば文字列リテラルなどを指定できません。
これらの齟齬から in ref
の記述が将来的な都合から非推奨となることになりました。
実際に使うと以下のような警告メッセージが
void test(in ref int n) { }
main.d(10): Deprecation: using `in ref` is deprecated, use `-preview=in` and `in` instead
これに加えて、-preview=in
を使っていると上記がエラーになります。
in
と ref
の記述順序によってエラーが微妙に変わるので、それぞれ ref
を取り除いてください。取り除いても挙動は最適化されますので問題ないはずです。
main.d(10): Error: attribute `ref` is redundant with previously-applied `in`
main.d(10): Error: attribute `in` cannot be added after `ref`: remove `ref`
非推奨 : 修飾されたオブジェクトをスローすることは非推奨となりました。
以前は、immutable
、const
、inout
、shared
で修飾された例外を throw
し、それを限定されない catch (Exception e)
節でキャッチすることができていました。
これは型安全性を壊すことになるため、修飾されたオブジェクトを throw
することは非推奨となりました。
これは、catch
句の中で immutable
な例外オブジェクトが変異する可能性を防ぐのに役立ちます。
また、ランタイムはスローされたオブジェクトを(例えばスタック・トレースを含むように)変更しますが、これは const
や immutable
なオブジェクトに対して不適切な影響をもたらす可能性があります。こういった理由からも、修飾されたオブジェクトをスローすることは非推奨となっています。
const
や shared
で修飾された例外を投げるコードがあれば、その修飾は適宜取り外してからスローしてください。(cast()
を付けてshared
を外すことができたりするので、その辺りは適当な記述にする必要があります)
実際に throw new const(Exception)("");
といったコードを書くと以下のような警告が出るようになります。
main.d(9): Deprecation: cannot throw object of qualified type `const(Exception)`
非推奨 : Bugzilla 11989: TickDuration
を非推奨にする
今回の変更の中では比較的影響を受ける可能性があるかもしれません。
core.time
に定義されている TickDuration
型を使う各種コードが deprecated
で修飾されて非推奨になりました。
参考: https://dlang.org/library/core/time/tick_duration.html
元々 TickDuration
というのは「モノトニック時刻」と呼ばれる単調増加が保証された時刻体系における時間情報を扱うものです。
OSの時計を巻き戻しても巻き戻ったりしないため、ゲーム内のFPS計算や処理時間の計測に用いられていました。
型としての TickDuration
の代わりには Duration
を使えば良いことになっています。
また、モノトニック時刻として現在時刻を取るためには、TickDuration.currSystemTick
を使っていた場合、 MonoTime.currTime
を使って MonoTime
型の時刻情報を利用すればOKです。
main.d(9): Deprecation: struct `core.time.TickDuration` is deprecated - TickDuration has been deprecated, please use Duration or MonoTime instead
main.d(9): Deprecation: function `core.time.TickDuration.currSystemTick` is deprecated - TickDuration has been deprecated, please use Duration or MonoTime instead
エラー : ;
を空文として使用することはエラーになりました。
2017年7月19日の dmd 2.075.0 から非推奨で警告が出ていた内容がエラーになりました。
ループの本文などで ;
を書いた場合、これ単体で空の文として成立するコードがエラーになります。
具体的な意図しないコードとして、以下のような罠が起こりえます。
void main()
{
foreach (i; 0 .. 8);
{
// ここに書かれたコードが実際にはループせず、1度しか実行されない。
// 理由は foreach の行の末尾に ; があるため…
}
}
この時に発生するエラーは以下のようなものです。
見つけたらバグの可能性があるので取り除くか、ループ自体が不要かもしれないので削除すると良いかと思います。
app.d(3): Error: use { } for an empty statement, not ;
まとめ
強化項目として数が多いわけではありませんが、ライブラリ強化、言語機能強化、ツール強化、エラーメッセージ改善、非推奨やエラーの対応、とかなり幅広く網羅的な対応のリリースでした。
リリースタイミングも前回から予定通り6月頭に行われており、開発チームとしても平常運転になっているようです。(DConfなどのイベントがあるはずですが、結構頑張っている?)
また今回ピックアップしませんでしたが、ImportC関連の強化が地味に色々行われており、知らない間にプロプロセッサが自動で処理されるようになっているようです。
これもどこかでまた記事にしたいと思います。
次回は順調にいけば8月、次のリリースでどのような話題が出てくるのか、期待して待ちたいと思います!