はじめに
D言語の公式コンパイラである dmd が更新され、最新バージョンである 2.107.0
が 2024/02/01 にリリースされました。
今回は前回からほぼ2ヶ月でのリリースで、コンパイラ周りの強化改善、特に内部構造の見直しや安全対策が目玉になっているようです。
今回も一通りの内容ご紹介していきます。
より細かい内容は下記リンクからChangeLogをご覧ください。
- ChangeLog
また、前回である2月版のリリースまとめは以下になります。
- D言語の更新まとめ 2023年12月版(dmd 2.106.0)
変更点目次
コンパイラ変更点
- アサート条件としての文字列リテラルは非推奨となりました。
- コンパイラの Makefile が整理されました。
- 識別できないプラグマはエラーではなく、単に無視されるようになりました。
- モジュールコンストラクターに
@standalone
が追加されました。 -
_d_newarray{mTX,miTX,OpT}
が単一のテンプレート_d_newarraymTX
に変換されました。
改善点
- Bugzilla 14387: アサーション条件として文字列リテラルを許可しないように変更しました
- Bugzilla 23629: importC: コードカバレッジ解析をサポートする必要があります
- Bugzilla 24069: ImportCは、名前のないパラメータとして関数ポインタを解析しません
- Bugzilla 24125: ImportC: ベクトル型の初期化子が理解されません
- Bugzilla 24155: ImportC: C23のデフォルト初期化子を受け入れます
- Bugzilla 24206: TypeSuffixを持つ型を返す関数型のエイリアスができません
- Bugzilla 24238: "lvalueではない"エラーメッセージが混乱を招く問題を改善しました
- Bugzilla 24247: $modifierオブジェクトを使用したコンストラクターが呼び出せないエラーを改善しました
- Bugzilla 24294: ImportC: gccで認識されないコマンドラインオプション -Wno-builtin-macro-redefined
- Bugzilla 24297: ImportCがglibc _FORTIFY_SOURCEと互換性がありません
ランタイム変更点
変更点
-
core.atomic
操作で無効なMemoryOrder
を使用することは、コンパイル時に拒否されるようになりました。 -
druntime
のMakefiles
が整理されました。 - 新しくCの
stdatomic
ヘッダーが追加されました。
改善点
- Bugzilla 20332: 連想配列の
clear
関数は@safe
であるべきです
ライブラリ変更点
変更点
-
isForwardRange
はオプションの要素型を受け取れるようになりました。 -
Makefiles
が整理されました。
改善点
- Bugzilla 11111:
std.algorithm.canFind
がNeedles...
をサポートするようになりました - Bugzilla 24075:
ushort
またはubyte
とともにtoChars
を使用できない
dlang.org 変更点
改善点
- Bugzilla 24176:
opApply
デリゲートのパラメータはref
である必要がなくなりました - Bugzilla 24177: 配列リテラルは期待される型に暗黙的に変換される可能性があります
- Bugzilla 24210: 関数の型がドキュメント化されていません
注目トピック
言語・コンパイラ変更点
識別できないプラグマはエラーではなく、単に無視されるようになりました。
あまり関係ない方も多いかもしれない変更です。
以前は、コンパイラが認識できないプラグマ(pragma(hoge, ...)
など)が使用された場合、エラーが発生していました。このエラーは、-ignore
オプションを使用しないと回避できませんでした。しかし、今回の変更により、認識できないプラグマは常に無視されるようになります。また、合わせて -ignore
オプションも無視されるようになりました。
この変更によって何が変わるかというと、開発者が様々なコンパイラの間で異なるプラグマが提供されていても同一ソースが利用できることにつながります。つまりソースの移植性が高まり、コンパイラによる条件分岐を書かなくて良くなるということです。
もう少し具体的に言えば、速い実行速度を求めてLDC向けにチューニングして書いたけどCIのテストやデバッグはDMDのほうが速いしpragma書き分けるか、みたいなケースで対処が不要になり考えることが減る、というのがおおよそのメリットかと考えられます。地味ですが生産性向上策ですね。
モジュールコンストラクターに @standalone
が追加されました。
今回の特徴的な強化の1つです。
モジュールコンストラクターに @standalone
属性が新たに追加されました。
この機能の目的は、2つのモジュールが相互にインポートされ、それぞれがモジュールコンストラクターを持つ場合、どちらのコンストラクターを先に実行すべきかランタイムが決定できずにエラーを発生させる状況を解決することです。
従来、pragma(crt_constructor)
を使用してこの問題を回避する方法がありますが、Cランタイムのコンストラクターを使う場合はDのランタイムがまだ初期化されていないため、ガベージコレクターをはじめとするいくつかの機能を利用できません。
そこで、@standalone
属性は、Dのランタイムが初期化された後に実行されるが、他のモジュールコンストラクターの実行を前提としないため、循環依存のエラーを回避できるモジュールコンストラクターを修飾するために使われます。これにより、開発者はプロジェクトのモジュール構造を維持しつつ、循環依存に関する問題を解決することが可能になります。
サンプル
import core.attribute : standalone;
immutable int* x;
@standalone @system shared static this()
{
x = new int(10);
}
void main()
{
assert(*x == 10);
}
この属性を使用する場合は、モジュールコンストラクターが他の変数の初期化に依存していないことを手動で保証する必要があり、そのため@system
または @trusted
のいずれかでマークする必要があります。可能な限り、@standalone
を使用する代わりに、問題のあるモジュールコンストラクターをより小さなモジュールに分割し、循環依存の問題を解決することが推奨されます。
core.attribute
にて提供されるため、import
して使うことがやや特徴的です。類似の例として @mustuse
という属性もありますが、新しく追加する属性が何かを壊すことが無いように、という配慮からこのような仕組みになっていると思われます。
プログラムのソースをほとんど変えることなく解決できるという点で、非常にプログラマーに配慮した変更かと思います。できればモジュールコンストラクタを整理しましょうということですが、こういった手段も同時に提供されるところがD言語らしさでしょうか。
_d_newarray{mTX,miTX,OpT}
が単一のテンプレート _d_newarraymTX
に変換されました。
コンパイラ(ランタイム?)内部実装が変更されました。
以前は、配列の初期化を行う際に_d_newarray{mTX,miTX,OpT}
という3つのテンプレート関数が使われていましたが、このリリースでは_d_newarraymTX
という単一のテンプレート関数に統合されました。この新しいテンプレート関数_d_newarraymTX
は、配列の要素の型に必要な初期化子の種類をDBI(Design by Introspection、内省による設計)を使用してチェックします。この統合によって、_d_newarraymTX
と_d_newarraymiTX
の冗長性がなくなり、_d_newarrayOpT
という汎用実装も不要になりました。
具体的には、以下のようなコード変換が行われます。
S[][] s = new S[][](2, 3)
// 内部的には以下のように変換される:
S[] s = _d_newarraymTX!(S[][], S)([2, 3]);
なお、この変更はcore.internal.array.construction
に新しいテンプレートを追加することによって実現されています。
その他、このようなテンプレートの集約を可能にするDesign by Introspectionが気になった方は検索するか、提唱者であるAndrei Alexandrescu氏のDConf2017講演動画を参照してみてください。
このような内部実装の改善は、D言語の将来のバージョンでさらに発展していく可能性がありますので、最新の情報に注意しながらパフォーマンス改善の恩恵を受けていきたいですね。
ランタイムの変更点
新しくCの stdatomic
ヘッダーが追加されました。
C言語からD言語への移植支援を目的として、Cの stdatomic
ヘッダーが追加されました。
stdatomic
ヘッダーは、C11標準で導入された、アトミック操作を行うための機能を提供するヘッダーファイルです。これにより、複数のスレッドが同時にデータにアクセスする際、データの整合性を保つためのいくつかの低レベル操作が可能になります。これはマルチスレッドプログラミングにおいて非常に重要な機能であり、特に並行処理やリアルタイムシステムの開発においては欠かせないものです。
D言語であれば既に core.atomic
がありますが、それと似たような機能を提供するものだと思っていただければ良いです。
stdatomic
を追加した主な目的は、CからDへコードを移植する際の労力を減らし、可能な限りCコンパイラと同様のコード生成を実現することです。これにより、Cで書かれた既存のライブラリやアプリケーションをD言語へ移植するプロセスが容易になるので、D言語とC言語の相互運用性が高まります。C言語やC++との連携はD言語の強みでもあるため、今後もこのようなヘッダー拡充は行われていくと思われます。
なお、stdatomic
モジュールを使用する際、コード生成の品質に特に注意が必要ない場合は、関数名に対応するエイリアスがない場合に _impl
を追加することで実装に直接アクセスすることができます。
生成されるコードの品質がシステムCコンパイラのものと比較して不十分であったり、使用上の問題となっている場合は、バグとして報告することが推奨されています。
ライブラリ変更点
isForwardRange
はオプションの要素型を受け取れるようになりました。
isForwardRange
関数に新しく2番目のテンプレート引数が追加されました。
これは、範囲の要素型を指定することを可能にします。指定された型と範囲の要素型が一致する場合はtrue
を返します。この機能により、プログラマーはより精密な型制約をコードに適用でき、意図しない型のデータが使用されることを防ぐことができます。
// 例:
static assert(isForwardRange!(int[], const(int))); // true
static assert(!isForwardRange!(int[], string)); // false
この変更によって、コンパイル時により精密な型チェックを行うことができるようになります。これは、プログラムの安全性を高め、意図しない型のデータが範囲内で使用されるのを防ぐのに役立ちます。
例えば、数値の配列に対して文字列を操作しようとする場合、この新しい isForwardRange
のチェックを用いると簡単に static assert
を使った検証が書けるため、謎の型不一致のエラーメッセージを回避したりできます。
ちなみに前回 2.106.0 でも同じような強化が isInputRange
に対しても行われました。似たようなテンプレートなので同様の措置が入ったと考えれば良いかと思います。
std.algorithm.canFind
が Needles...
をサポートするようになりました
D言語の標準ライブラリにあるstd.algorithm
モジュールのcanFind
関数が強化されました。
この更新では、canFind
関数で複数の検索キー(Needles...
)のサポートが追加されています。これは、要するに「検索する値」として何を受け入れるか、という制約が緩まった、ということです。配列やレンジに対して、一つまたは複数の要素を簡単に検索できるようになります。
従来、canFind
関数は一つの要素またはレンジを検索する機能を提供していましたが、この強化により、複数種類の検索値(Needles
)を同時に検索することが可能になります。
内部的には、find(haystack, needles)
関数を呼び出し、その中で startsWith
関数を使用します。このstartsWith
関数は、要素と範囲の両方を受け入れられるため、表面的な制約を外すだけで柔軟な検索が行えるようになります。
言葉で表現するのも難しいので、更新前と更新後のユニットテスト例を通じて、具体的な使用方法を示します。
更新前のユニットテスト
const arr = [0, 1, 2, 3];
assert(canFind(arr, 2));
assert(!canFind(arr, 4));
assert(canFind(arr, [1, 2], [2, 3]));
assert(canFind(arr, [1, 2], [2, 3]) == 1); // 1番目の検索キーが最初に見つかった
assert(canFind(arr, [1, 7], [2, 3]));
更新後のユニットテスト
const arr = [0, 1, 2, 3];
// 中略
// 複数の検索キーで1つを検索
assert(arr.canFind(2, 3) == 1);
assert(arr.canFind(3, 2) == 2); // 2番目の検索キーが最初に見つかった
assert(arr.canFind([1, 3], 2) == 2); // 配列と要素が混在した検索もできるようになった
assert(arr.canFind([0, 1], 2) == 1); // 配列を指定するとちょっとわかりづらい?
assert(arr.canFind([1, 2], 2) == 1); // 1,2が先に見つかる
assert(arr.canFind([2, 3], 2) == 2); // 単独の2が先に見つかる
非推奨または廃止される機能
今回は、非推奨とエラーが1件ずつ、合計2件です。
また今後の予定などは以下のページにまとまっていますので、こちらも参考としてみてください。
非推奨
- アサート条件としての文字列リテラルは非推奨となりました。
削除/エラー
-
core.atomic
操作で無効なMemoryOrder
を使用することは、コンパイル時に拒否されるようになりました。
非推奨 : アサート条件としての文字列リテラルは非推奨となりました。
文字列リテラルをアサート条件に使用することが非推奨とされました。これは、開発者が予期せず、不適切な使い方をしてしまう可能性があるためです。
たとえば次のようなコードがあります。
assert("unexpected runtime condition");
static assert("unhandled case for `", T, "`");
これらの assert
は、開発者の期待に反して、常に効果を発揮しないままになっています。assert
の第1引数は条件値、つまり、文字列なのですが、それが null
ではないので常に true
と評価されています。結果、この assert
が失敗することはない状況です。
元々意図していたところとしては、恐らく assert(0, "message")
と書くべきところで 0
や false
を省略してしまい、意図せず assert("message")
と書いてしまうことにあります。
というわけで、このリリースからこのような使用法は非推奨とされ、開発者に警告が表示されるようになりました。
source\app.d(5,9): Deprecation: assert condition cannot be a string literal
source\app.d(5,9): If intentional, use `"unexpected runtime condition" !is null` instead to preserve behaviour
もし、文字列リテラルをアサート条件として意図的に使用したい場合は、次のように書くことで回避できます。
assert("" !is null);
static assert("" !is null);
この変更により、assert
の使用方法がより明確になり、意図しない使い方によるバグを減らすことができます。
影響する方は多いかもしれませんが、しっかりと assert
を書くことで安全なプログラミングができますので頑張りどころですね。
削除/エラー : core.atomic
操作で無効な MemoryOrder
を使用することは、コンパイル時に拒否されるようになりました。
今回のリリースにおける比較的重要な変更点です。
core.atomic
モジュールのいくつかの関数で、無効なMemoryOrder
を使用しようとするとコンパイル時にエラーが発生するようになりました。
信頼性や安全性の改善施策と言えるかと思いますが、対象となる関数とパターンが色々あるので細かく整理していきます。
atomicLoad
と atomicStore
これらの関数は、MemoryOrder.acq_rel
引数とともに使用されると、コンパイル時にエラーとなります。これまでは、atomicLoad
はMemoryOrder.rel
を、atomicStore
は MemoryOrder.acq
を拒否していましたが、これからは両関数が MemoryOrder.acq_rel
も拒否するようになります。
// エラー例:
atomicLoad!(MemoryOrder.acq_rel)(src);
atomicStore!(MemoryOrder.acq_rel)(dest, value);
// 修正方法:
atomicLoad!(MemoryOrder.seq)(src);
atomicStore!(MemoryOrder.seq)(dest, value);
// または:
atomicLoad(src);
atomicStore(dest, value);
エラーメッセージ
C:\D\dmd2\windows\bin64\..\..\src\druntime\import\core\internal\atomic.d(57,9): Error: static assert: "invalid MemoryOrder for atomicLoad()"
(中略)
source\app.d(10,34): instantiated from here: `atomicLoad!(MemoryOrder.acq_rel, shared(int)*)`
atomicExchange
atomicExchange
関数は、MemoryOrder.acq
引数とともに使用されるとエラーになります。
// エラー例:
atomicExchange!(MemoryOrder.acq)(dest, value);
// 修正方法:
atomicExchange!(MemoryOrder.seq)(dest, value);
// または:
atomicExchange(dest, value);
エラーメッセージ
C:\D\dmd2\windows\bin64\..\..\src\druntime\import\core\internal\atomic.d(287,9): Error: static assert: "Invalid MemoryOrder for atomicExchange()"
(中略)
source\app.d(13,34): instantiated from here: `atomicExchange!(MemoryOrder.acq, int, int)`
atomicCompareExchangeWeak
と atomicCompareExchangeStrong
この2つはDMDなどのコンパイラ内部で使われているものなので、あまり目にすることはないかもしれません。(core.internal.atomic
で定義されていて、core.atomic
の内部実装で使われる)
これらの関数は、2番目の fail
引数が MemoryOrder.rel
または MemoryOrder.acq_rel
である場合にエラーを発生させます。また、fail
引数が第一の succ
引数より大きい値であるときもエラーとなります。この場合 、MemoryOrder.raw
を使用することが推奨されます。
// エラー例:
atomicCompareExchangeWeak!(MemoryOrder.rel, MemoryOrder.rel)(dest, compare, value);
atomicCompareExchangeStrong!(MemoryOrder.acq, MemoryOrder.rel)(dest, compare, value);
// 修正方法:
atomicCompareExchangeWeak!(MemoryOrder.rel, MemoryOrder.raw)(dest, compare, value);
atomicCompareExchangeStrong!(MemoryOrder.acq, MemoryOrder.raw)(dest, compare, value);
エラーメッセージ
C:\D\dmd2\windows\bin64\..\..\src\druntime\import\core\internal\atomic.d(326,9): Error: static assert: "Invalid fail MemoryOrder for atomicCompareExchangeStrong()"
source\app.d(16,62): instantiated from here: `atomicCompareExchangeStrong!(MemoryOrder.rel, MemoryOrder.rel, shared(int))`
C:\D\dmd2\windows\bin64\..\..\src\druntime\import\core\internal\atomic.d(326,9): Error: static assert: "Invalid fail MemoryOrder for atomicCompareExchangeStrong()"
source\app.d(17,64): instantiated from here: `atomicCompareExchangeStrong!(MemoryOrder.acq, MemoryOrder.rel, shared(int))`
メモリオーダリングに手を出す方ならご存知の範囲なのではと思わなくもないですが、このような安全のための変更もありますので引き続き非推奨などの情報は追いかけていきたいと思います。
まとめ
しばらく更新記事を書いておらず2日連続で書きました。今日もAIさんに助けられています。
今回は紹介していませんが、Makefileの更新など、内部の改善に集中されていたリリースだったようです。
一報でライブラリの改善なども多く取り入れられていますので、ぜひとも最新リリースをお試しいただければと思います。
assert
とか間違ってると地味に怖いですしね。
というわけで今回の翻訳記事は以上です。
D言語便利に使いましょう!