9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

D言語の更新まとめ 2024年2月版(dmd 2.107.0)

Last updated at Posted at 2024-03-09

はじめに

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

今回は前回からほぼ2ヶ月でのリリースで、コンパイラ周りの強化改善、特に内部構造の見直しや安全対策が目玉になっているようです。
今回も一通りの内容ご紹介していきます。

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

また、前回である2月版のリリースまとめは以下になります。

変更点目次

コンパイラ変更点

  • アサート条件としての文字列リテラルは非推奨となりました。
  • コンパイラの 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 を使用することは、コンパイル時に拒否されるようになりました。
  • druntimeMakefiles が整理されました。
  • 新しくCの stdatomic ヘッダーが追加されました。

改善点

  • Bugzilla 20332: 連想配列の clear 関数は @safe であるべきです

ライブラリ変更点

変更点

  • isForwardRange はオプションの要素型を受け取れるようになりました。
  • Makefiles が整理されました。

改善点

  • Bugzilla 11111: std.algorithm.canFindNeedles... をサポートするようになりました
  • 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.canFindNeedles... をサポートするようになりました

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") と書くべきところで 0false を省略してしまい、意図せず 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を使用しようとするとコンパイル時にエラーが発生するようになりました。
信頼性や安全性の改善施策と言えるかと思いますが、対象となる関数とパターンが色々あるので細かく整理していきます。

atomicLoadatomicStore

これらの関数は、MemoryOrder.acq_rel 引数とともに使用されると、コンパイル時にエラーとなります。これまでは、atomicLoadMemoryOrder.rel を、atomicStoreMemoryOrder.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)`

atomicCompareExchangeWeakatomicCompareExchangeStrong

この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言語便利に使いましょう!

9
3
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
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?