3
1

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年6月版(dmd 2.109.0)

Last updated at Posted at 2024-10-17

はじめに

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

今回は前回からほぼ2ヶ月でのリリースで、中々大きな強化が連続しているあまり見ないリリースです。1個1個のボリュームがあり、ちょっと翻訳や紹介が大変です。
前回に続き、記事にするのがかなり遅れていますが、なんとか今回も一通りの内容ご紹介していきます。

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

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

変更点目次

言語・コンパイラ変更点

変更点

  • [next edition] 型の インスタンス のメンバーにエイリアスを付けることがエラーとなりました。
  • ビットフィールドのイントロスペクション機能が追加されました。
  • __ctfeWrite が追加され、CTFEからメッセージを出力できるようになりました。
  • 非推奨警告が -verrors によって制限されるようになりました。
  • dtoh ツールが、extern(Windows) および extern(System) 関数のシグネチャを生成できるようになりました。
  • 動的配列に対する foreach で、インデックス型として size_t より小さな型を使用できるようになりました。
  • デリゲートに対する foreach_reverse がエラーとなるようになりました。
  • C23に準拠する新しい文字を認識できるよう、識別子テーブルが拡張され、CLIでの設定が可能になりました。
  • ImportCにおけるUnicodeサポートが向上しました。
  • 不足しているシンボルに関するエラーメッセージがより分かりやすくなりました。
  • WindowsのOMFサポートが削除されました。

改善点

  • Bugzilla 5573: main() が不足している場合、コンパイラはエラーとするべきです。
  • Bugzilla 21718: プレビュー版のスイッチの説明が不十分である。
  • Bugzilla 24111: [ImportC] fatal error C1034: stdio.h : インクルードパスが設定されていません。
  • Bugzilla 24450: 配列の長さが既知である場合、foreach インデックスに対してVRP(訳注: Value Range Propagation、値範囲推定)を適用します。
  • Bugzilla 24452: 実行時にコードカバレッジを無効化できません。

ランタイム変更点

変更点

  • モジュール core.sys.linux.sys.mount が追加されました。
  • druntime からすべての collectNoStack 関数とAPIが削除されました。
  • Thread.sleep@trusted としてマークされました。

ライブラリ変更点

変更点

  • プロセス実行時の設定として std.process.Config.preExecDelegate が追加されました。

dlang.org変更点

改善点

  • Bugzilla 24488: ホームページから貢献者ガイドが見つけにくい問題を改善しました。

注目トピック

言語・コンパイラ変更点

ビットフィールドのイントロスペクション機能が追加されました

ビットフィールドとは、元々C言語で用意されている「特定のバイト領域をビットレベルで分割、複数のフィールドに見せかける」という機能です。
D言語では、バージョン 2.101.0 からD言語のソースで同様の記法をサポートできるようになっています。

参考

とはいうものの、機能自体にあまり馴染みがないかもしれませんね。

以下の機能が追加されました:

  • プロパティ .bitoffsetof により、ビットフィールドの開始ビット番号を取得できます。
  • プロパティ .bitwidth により、ビットフィールドのビット数を取得できます。
  • __traits(isBitfield, symbol) によって、フィールドシンボルがビットフィールドであるかどうかを確認でき、ビットフィールドであれば true を返します。

記述のサンプルは以下の通りです。ビットフィールドを使うにあたって、-preview=bitfields フラグを付ける必要があるので注意してください。

struct S {
    int a : 3;
    int b : 5;
}

void main() {
    S s;
    pragma(msg, S.a.bitoffsetof); // 0
    pragma(msg, S.a.bitwidth); // 3
    pragma(msg, S.b.bitoffsetof); // 3
    pragma(msg, S.b.bitwidth); // 5
    pragma(msg, __traits(isBitfield, S.a)); // true
}

補足ですが、Bitfield自体はC言語の機能なので、C言語と連携する多くの言語で似たような機能が提供されています。
Nimでは .bitsize、Zigでは packed struct および u29 みたいな型が提供されています。(無い言語もありますが)

今回D言語は、ビットフィールドの情報を取得するためのイントロスペクション(内省、D言語では大体「メタプログラミング用」くらいの意)機能を提供する流れになりました。
これを使うことでコンパイル時にビットフィールドの情報を取得できるので、より柔軟で汎用性の高いライブラリを書けるようにすること、D言語のメタプログラミング機能強化が主な目的と言えそうです。

メタプログラミングに興味がある方は、ぜひこの機能を使って遊んでみてください。

__ctfeWrite が追加され、CTFE からメッセージを出力できるようになりました

__ctfeWrite といういかにも怪しげ?な名前の関数が追加されました。

これは、CTFE (Compile-Time Function Evaluation) の最中に何かメッセージを出力するための関数だそうです。

例えば、以下のように使用します。

int greeting() {
    __ctfeWrite("CTFEからのメッセージです。今日の日付は ");
    __ctfeWrite(__DATE__);
    __ctfeWrite("\n");
    return 0;
}

enum forceCTFE = greeting(); // メッセージが出力される

void main()
{
    greeting(); // 何も出力されない
}

このプログラムをコンパイルすると、次のような出力が生成されます:

CTFEからのメッセージです。今日の日付は Oct 17 2024

これとよく似た機能に pragma(msg, ...) がありますが、具体的に何が違うのでしょうか?
ちょっと実験した限り、主に以下の特徴が共通点と差異かなと思われます。

共通点

  • どちらも出力先は stderr である。
  • コンパイル時に実行されたら出力され、通常の実行では出力されない。
  • ロジック的に通らない場合は出力されない。(static if で通らない pragma(msg, ...) は出力されない、if で通らない __ctfeWrite も出力されない)

差異

  1. __ctfeWrite は関数の引数など実行時の値を使えるが、pragma(msg, ...) はコンパイル時の値のみ。
  2. pragma(msg, ...) は末尾に改行が含まれるが、__ctfeWrite には含まれない。
    • pragma だと1つの引数にまとめないと1行に出せなかったが、__ctfeWrite は複数回呼び出しても1行にまとめて出力される。
  3. pragma のほうが引数の解釈が柔軟、__ctfeWriteconst(char)[] に限定される。
    • __ctfeWrite は文字列限定ですが、pragma(msg, ...) は型を渡すと勝手に型名を表示してくれたりします。
    • とはいえコンパイル時に std.format などを使えば特に困らないと思います。あとは T.stringof で名前文字列を得る方法も覚えると便利です。
  4. 混在すると pragma(msg, ...) のメッセージは該当行ですぐに出力されるようだが、__ctfeWrite はバッファリングされている(っぽい)。
    • __ctfeWrite => pragma(msg, ...) の順に書いたところ、pragma(msg, ...) のメッセージが先に出力されるため。
    • これは正直コメントに困りますが、Flushのコストなどを考えるとコンパイル時のパフォーマンスで __ctfeWrite のほうが有利かもしれません。

というわけで、コンパイル時に凝ったメッセージを出すような場合にぜひ使ってみてください。
std.format などと合わせてこちらも覚えておくと良いかもしれません。

非推奨警告が -verrors によって制限されるようになりました

-verrors は、コンパイル時に設定することでエラーメッセージの表示数を制限するためのフラグです。

デフォルトでは、コンパイラは20個のエラーメッセージが表示された時点で停止しますが、例えば -verrors=50 や無制限にするための -verrors=0 といったフラグを渡すことで、異なる数に変更できます。

このエラー制限が、今回から非推奨メッセージにも適用されるようになりました。

非推奨関数fを4回呼ぶサンプル
deprecated void f() {}

void main() {
    f();
    f();
    f();
    f();
}
-verrorsを3に設定
> dmd -verrors=3 app.d
app.d(7): Deprecation: function app.f is deprecated
app.d(8): Deprecation: function app.f is deprecated
app.d(9): Deprecation: function app.f is deprecated
1 deprecation warning omitted, use `-verrors=0` to show all

これでDeprecationメッセージもエラーメッセージと同じ制限に従うようになりました。

元々非推奨ログは20個で止まるということもなかったので、何かの拍子に膨大な非推奨ログが出るとCI/CDで保存するログが不必要に大きくなってしまうのもあり、そのような状況の対策が取られたということのようです。

dtoh ツールが、extern(Windows) および extern(System) 関数のシグネチャを生成できるようになりました。

これはD言語で書いたソースを元にして、C言語で読み込むヘッダーを自動生成する時の話です。

D言語ではコンパイル時に -HC スイッチを使用することでC言語用のヘッダーが出力されるのですが、これまでの extern(C) および extern(C++) 関数に加えて、extern(Windows) および extern(System) 関数も .h ファイルに出力されるようになりました。
ちなみに extern(Windows)extern(System) も、Windows環境での関数呼び出し規約(Win32API等で使う__stdcall)を指定するためのもので同じ意味です。

実際の例は以下のようになります。

サンプルコード
extern(Windows) int hello() {
    return 0;
}

extern(System) int myFriend() {
    return 0;
}
実行
dmd -HC sample.d
生成された.hファイル
#ifndef _WIN32
#define EXTERN_SYSTEM_AFTER __stdcall
#define EXTERN_SYSTEM_BEFORE
#else
#define EXTERN_SYSTEM_AFTER
#define EXTERN_SYSTEM_BEFORE extern "C"
#endif

int32_t __stdcall hello();

EXTERN_SYSTEM_BEFORE int32_t EXTERN_SYSTEM_AFTER myFriend();

というわけで、-HC を付けてコンパイルすると __stdcall などの呼び出し規約が付いた関数のシグネチャが生成されました!便利ですね!
extern(XXX) は他にもあるので、今後 extern(Pascal) とかも追加されるかもしれません。

動的配列に対する foreach で、インデックス型として size_t より小さな型を使用できるようになりました。

こちらはコンパイラの最適化に関する変更です。

次のケースでは、foreach の対象に取る動的配列の長さがコンパイル時に判明しているとみなされ、最適化が行われるようになります。

  • 配列がリテラルである場合
  • 配列が、上限がコンパイル時に判明しているスライス式である場合

具体的には以下のようなイメージです。
この時、インデックス型は size_t より小さな型を使用できます。

サンプルコード
import std;

void main() {
    // 配列がリテラルの場合、明らかに長さが5であるとわかる
    foreach (ubyte i, e; [1, 2, 3, 4, 5]) {
        writeln(i, ":", e);
    }

    // スライス式の上限がコンパイル時にわかる場合
    int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    foreach (ubyte i, e; a[0 .. 5]) {
        writeln(i, ":", e);
    }
}

以前はこれをやると以下のような警告が出ていました。

onlineapp.d(6): Deprecation: foreach: loop index implicitly converted from `size_t` to `ubyte`
onlineapp.d(13): Deprecation: foreach: loop index implicitly converted from `size_t` to `ubyte`

ちなみに、この変更は互換性のため ubyte i など型を明示したときに限ります。型を省略した場合は従来通り size_t が使われるので、勝手に速くなるとかではないことに注意が必要です。

というわけでこの変更をまとめると、「foreach のインデックス型に小さな型を指定しても警告が出なくなって最適化されるようになりました」ということですね。

C23に準拠する新しい文字を認識できるよう、識別子テーブルが拡張され、CLIでの設定が可能になりました

今回の目玉機能です!

D言語では、新たに 識別子 に使える文字の範囲を柔軟に設定できる機能が追加されました。
ここで言う 識別子 とは、プログラム内で変数や関数に名前をつける際に使う文字列のことです。

この変更により、なんとついに変数名や関数名に日本語が使えるようになりました!さらに、使って良いかどうかも設定できるようになりました!!!

たとえば、以下の選択肢から識別子のルールを選ぶことができます。

  • C99: 1999年に制定されたC言語の規則で、基本的に英数字とアンダースコアのみが使用可能です。
  • C11: 2011年のC言語の規則で、C99とほぼ同様ですが、一部の改善が含まれています。
  • C23 (UAX31): 最新のC言語の規則で、Unicode文字を識別子に含めることができるため、英語以外の文字も使用可能です。
  • All: 可能な限り多くの文字セットを含む最も柔軟なルールです。(既定値はこれのようです)

C23(UAX31)で何が使えるか気になりますが、Unicodeの国際標準ですので気になる方は詳細については以下を参照してください。

さて、この機能は次のようなコンパイラオプションにより設定できます。

  • D言語の場合 : -identifiers=<table>(例:-identifiers=c99
  • ImportCの場合 : -identifiers-importc=<table>(例:-identifiers-importc=c11

この新機能のメリットは、何と言っても様々なプロジェクトでローカライズされた適切な識別子を定義できるようになる、ということです。
たとえば、日本語でしか表現できない固有名詞やその他の非ASCII文字を使ったプログラムでも、今回からそのまま利用できます。
専門用語でもなんでもどんとこい!ということで、どんどん使っていきましょう!

ImportCにおけるUnicodeサポートが向上しました

前述の識別子テーブルの拡張の付属のような強化です。

ImportCで扱うC言語のソースで、識別子の一部に \uXXXX \UXXXXXXXX という形式の構文を使用できるようになりました。

たとえば以下のようなソースは、今回の変更により問題なくコンパイルできるようになります。

int \u30b3\u30f3\u30d1\u30a4\u30eb = 42; // \uXXXX を元に戻すと「コンパイル」という日本語になります

void \U0001F600(void) { // \UXXXXXXXX を元に戻すと「😀」という絵文字になります
    printf("Hello, world!\n");
}

ちなみにちょっとテストしてみると、上記の「コンパイル」に戻る変数の方は難なく使えるのですが、絵文字の関数はそのまま呼び出すことができませんでした。(関数呼び出すところで構文エラーになる)

外部から与えられたライブラリでどうしてもその関数を呼びたい場合は、通常の識別子の範囲でラッパーを書くか、pragma(mangle, ...) などを使って無理やり名前を変えてリンクするとかが無難かもしれません。(試していません。というかCでこれ呼べるの?)

不足しているシンボルに関するエラーメッセージがより分かりやすくなりました。

D言語の最新アップデートでは、リンク時のシンボル不足のエラーがより理解しやすくなり、デバッグ作業が簡単になりました。

シンボルエラーとは、プログラムの開発中、ライブラリを正しくリンクしていなかったり、main 関数を含め忘れたときに起きるエラーです。
このシンボルエラーとは、「プログラム内で使用している関数や変数が見つからない」という問題なのですが、これまでのD言語ではこの見つからないシンボル名を「マングルされた状態」(コンパイラに都合の良い形式)で表示されていました。これにより、エラーメッセージが非常に分かりにくいという状況でした。

例えば、次のコードで assertEquals 関数を使用した場合を考えます。

module app;

unittest
{
    import assertions;
    assertEquals('D', 'D');
}

これを dmd -unittest app.d でコンパイルすると assertions なんてモジュールは丸ごと未定義なので、リンク時に「未定義シンボル」というエラーが発生します。
以前のバージョンでは、エラーメッセージに "_D10assertions12assertEqualsFaaZv" といったマングルされた名前が表示されていました。かろうじて assersionsassertEquals が拾い読みできるかもしれませんが面倒ですね。

しかし、今回の変更では、このシンボル名が自動的にデマングル(読みやすい形に変換)され、エラーメッセージが次のように改善されます。

Error: undefined reference to `void assertions.assertEquals(char, char)`

これにより、エラーメッセージを見ただけで「どの関数が未定義なのか」「何を修正すべきか」が一目で分かるようになります。
また、ライブラリを作っていたり単体テストプロジェクトを作ると main 関数が不足したりしますが、その場合は特別に次のような具体的な提案が表示されます。

Error: undefined reference to `main`
perhaps define a `void main() {}` function or use the `-main` switch

なおChangeLog原文いわく、「経験豊富なユーザーなら、シンボルをデマングルして読みやすくする方法を知っているかもしれません」、って以下の方法が書かれていますが1ミリも知りませんでした。

> echo _D10assertions12assertEqualsFaaZv | ddemangle
void assertions.assertEquals(char, char)

今回の改善により、エラー発生時に時間をかけてシンボル名を手動でデマングルする必要がなくなりました。
エラーメッセージの改善は幅広い層にとって有益なので、今後もこういった改善が続いていくと良いですね!

サポートされているリンカー

この改善は、ldbfdgoldmold、および Microsoft LINK といった一般的なリンカーでサポートされているそうです。
もしお使いのリンカーが見当たらない場合は開発チームに報告してみてください。

ランタイム変更点

core.sys.linux.sys.mount モジュールの追加

Linuxのファイルシステム関連機能を提供する <sys/mount.h> に対応するモジュールとして、core.sys.linux.sys.mount が追加されました。
よく見ると sys が2個あるのでちょっと気になりますが、core.sys.linuxcore.sys.windows までで1つの区切り、その後は従来のヘッダーパス、というルールなので仕方なしでした。

さて、このヘッダーファイルは、ファイルシステムのマウントやアンマウントを含むシステムコールの定義を含むので、関連するユーティリティを作る場合には重宝するかもしれません。
これまでに使ったことがある方は、こちらも便利に使っていただければと思います!

druntime からすべての collectNoStack 関数とAPIが削除されました。

一見何のことかと思いますが、D言語のランタイムの内部実装に関する話題です。
今回、D言語のガベージコレクタの実装から collectNoStack という関数が削除されました。結果として、マルチスレッドで動作するプログラムにおいて、安全性が向上したそうです。

変更の背景として、プログラム終了時のクリーンアップ処理において、この collectNoStack という関数がGCのスタックルートを考慮せずにメモリを収集することがあり、マルチスレッドプログラムにおいて危険な挙動を引き起こす可能性があった、とのことです。バッサリ削除したので安心、という感じですかね。

しかし今回の変更によって、プログラム終了時にメモリがクリーンアップされない、また以前はクリーンアップされていたメモリがクリーンアップされなくなる場合があります。
とはいえ通常はプログラムが終了すれば、すべてのメモリはOSによって解放されますので問題にはなりません。これは仕様として許容されている範囲だそうです。

プログラム終了時の挙動がおかしいなどを感じたら、実行時引数として --DRT-gcopt=cleanup:finalize を付けることで、すべてのメモリをクリーンアップすることもできます。

実行時引数は以下のように指定します。

実行例(Linux)
> app --DRT-gcopt=cleanup:finalize
実行例(Windows)
app.exe --DRT-gcopt=cleanup:finalize

また、こちらのフラグの仕様は以下のページにあります。

設定をソースに埋め込むこともでき、以下のような記述になります。

extern(C) __gshared string[] rt_options = [ "gcopt=initReserve:100 profile:1" ];

その他フラグの解説記事はこちらです。

余談ですが、この --DRT-gcopt=cleanup:finalize は今回強化されたわけではなく、もうずっと前から存在するフラグです。用意が良いですね!

Thread.sleep@trusted マーク追加

Thread.sleep メソッドが @trusted としてマークされ、@safe コードから使用できるようになりました。

個人的にスレッドを待機するのはサンプルあるあるなのでよく使うんですが、実は @safe から呼べなかったんですね。気付かなかった。

今回のテーマである @trusted@safe は、プログラムが未定義動作を起こさないようにする安全性レベルみたいな属性です。
@trusted は手動で安全性を確認したコードに付けられるもので、最も安全な @safe でマークされたコードから直接呼び出すことができます。
@safe でも @trusted でもないものは、@system と呼ばれ、OSやハードウェアに依存するコードで使われます。@system な関数は、@safe から呼び出すことができません。

今回この変更により、スレッドの一時停止処理が安全であることが保証され、マルチスレッドのシステムでも安心して使用できることになります。

せっかくなので、これを機に全面的に @safe なマルチスレッドのプログラムを書いてみるのもいいかもしれませんね!

プロセス実行時の設定として std.process.Config.preExecDelegate が追加されました。

std.processexecutespawnShell などを呼び出すときのオプションが強化されました。
具体的には、std.process.Config.preExecDelegate が追加され、プログラム実行前に環境をキャプチャしながら事前処理を行う機能が強化されています。

std.process.Config には、元々 preExecFunction という設定があり、プログラムの実行前に呼び出す関数を設定するものです。
この設定では、実行する関数に function を要求しており、環境のキャプチャ(周囲のローカル変数にアクセス出来るよう保証すること)ができませんでした。

今回追加された preExecDelegate は、delegate を指定できるので周囲の変数などをキャプチャして使うことも可能で、より柔軟な処理が可能になります。

これはChangeLogのサンプルですが、Linuxの prctl システムコールを使用して、プロセスの起動時に特定のシグナル処理を安全に実行できます。

import core.sys.linux.sys.prctl : PR_SET_PDEATHSIG, prctl;
import std.process : Config, execute;

void runProgram(int pdeathsig) {
    execute(
        ["program"],
        config: Config(
            preExecDelegate: () @trusted => prctl(PR_SET_PDEATHSIG, pdeathsig, 0, 0, 0) != -1, // pdeathsig を使っているが、これはexecute関数でいつ利用されるか不明であり(コピー)キャプチャが必要
        ),
    );
}

非推奨または廃止される機能

今回は、非推奨化は無し、廃止が3件で計3件の機能が廃止となります。

現時点ではまったく影響のなさそうなものが多いですが、一つずつ解説していきます。

また今後の予定などは以下のページにまとまっていますので、こちらも参考としてみてください。

削除/エラー

  • [next edition] 型の インスタンス のメンバーにエイリアスを付けることがエラーとなりました
  • デリゲートに対する foreach_reverse がエラーとなるようになりました
  • WindowsのOMFサポートが削除されました

削除/エラー

[next edition] 型の インスタンス のメンバーにエイリアスを付けることがエラーとなりました。

[next edition] とある通り、現在のバージョンにおける変更ではありません。
色々試しましたが何かフラグを立てないと動作は変わらないようで、単に予告と考えれば良さそうです。

ひとまずサンプルコードから見てみましょう。

サンプルコード
struct Foo {
    int v;
    void test(Foo that) const {
        alias a = this.v; // OK
        alias b = that.v; // エラー、`typeof(that).v` を使用する必要があります
        assert(&a is &b); // 成功
        assert(&b !is &that.v);
    }
}

struct Bar {
    Foo f;
    alias v = f.v; // エラー、`typeof(f).v` を使用する必要があります
}

エラー箇所は alias b = that.v;alias v = f.v; です。
alias v = f.v; はまだわかりますが、alias b = that.v; がエラーになる理由がよくわからないですね。

ただ、対処としては typeof を付ければ良いとのことです。
試せないのでエラーメッセージも見れないのが残念ですが、サンプルコードの例から恐らくメッセージに対処方法も含まれるパターンだと思います。
メッセージを見たらまず読んで、指示に従って直してみてください。

[next edition] の試し方調べないとなぁ)

foreach_reverse on a delegate is now an error

Using foreach_reverse with a delegate is now an error.

デリゲートに対する foreach_reverse がエラーとなるようになりました。

随分古く(追える限り2012年の2.060.0)から元々非推奨として警告が出ていた機能が今回エラーになりました。
これによって何か被害を受ける方はほぼいないと思われますが、(ちょっと面白いので)念のための解説です。

今回テーマとなるのが foreach_reverse、これは配列やスライスなどの反復可能なオブジェクトに対して「逆順」で反復処理を行うための構文です。
これに加え、元々 foreach で配列などを指定する代わりに「デリゲートを指定する」という特殊な機能があり、これらを組み合わせたケースが今回該当します。(後述しますが、仕組みは opApply と同じです)

Foreach over delegates: https://dlang.org/spec/statement.html#foreach_over_delegates

まずはサンプルコードから。

// ループ処理を定義する関数を用意
int myLoop(scope int delegate(int) dg)
{
    for (int z = 1; z < 128; z *= -2)
    {
        auto ret = dg(z);

        if (ret != 0)
            return ret;
    }
    return 0;
}

int[] result;
foreach (x; &myLoop) // デリゲートを渡す
{
    result ~= x;
}
assert(result == [1, -2, 4, -8, 16, -32, 64, -128]);

foreach_reverse (x; &myLoop) // 逆順でデリゲートを渡すが、これはエラーになる
{
    result ~= x;
}
エラーメッセージ
onlineapp.d(25): Deprecation: cannot use `foreach_reverse` with a delegate

元々この Foreach over delegates は、「順方向」の列挙しかサポートしていません。
foreach_reverse でも逆順に回ることはなく、順方向と結果は同じです。

というわけでエラーの対処としても、「foreach_reverseforeach にすることで動作はそのままでエラーが消える」ということになります。

いやいや逆順が正解なんだよ!という場合は、1回結果を取り出してから逆順に処理するなどの方法で良さそうです。
書き方は色々あると思いますが、一番簡単な実装は以下のような感じでしょうか。

一度取り出して逆順に処理する
int[] result;
foreach (x; &myLoop)
{
    result ~= x;
}

foreach_reverse (x; result) // 改めて逆順でループ
{
    writeln(x);
}

気になった方がいるかもしれないので補足すると、opApply も、Foreach over delegatesと同様に順方向の列挙しかサポートしません。
仮にこれをやると、今も昔もしっかりエラーになります。

struct Test
{
    // ループ処理を定義する関数を用意
    int opApply(scope int delegate(int) dg)
    {
        for (int z = 1; z < 128; z *= -2)
        {
            auto ret = dg(z);

            if (ret != 0)
                return ret;
        }
        return 0;
    }

}

void main()
{
    Test t;
    int[] result;
    foreach_reverse (x; t) // 逆順でデリゲートを渡すが、これはエラーになる
    {
        result ~= x;
    }
}
onlineapp.d(22): Error: invalid `foreach_reverse` aggregate `t` of type `Test`
onlineapp.d(22):        `foreach_reverse` works with bidirectional ranges (implementing `back` and `popBack`), aggregates implementing `opApplyReverse`, or the result of an aggregate's `.tupleof` property
onlineapp.d(22):        https://dlang.org/phobos/std_range_primitives.html#isBidirectionalRange

エラーにもありますが、opApplyReverse という関数を実装することで逆順での列挙をサポートすることができる、つまりこちらは元々想定されています、というオチでした。

opApplyReverseの仕様

ちょっと蛇足が長くなりましたが、このエラーに遭遇した場合は以下のいずれかの対処を取ってもらえればと思います。

  1. foreach_reverseforeach にする(動作はそのままでエラーが消える)
  2. デリゲートを順方向の foreach で呼び出して1度結果を取り出してから再度 foreach_reverse を使い逆順で処理する
  3. 構造体などにラップしたうえで opApplyReverse を実装して、正式に逆順での列挙をサポートする

WindowsのOMFサポートが削除されました。

今回の更新(dmd 2.109.0)で、Windows向けのOMF(Object Module Format)サポートが廃止されました。

関係する方は多くないと思いますが、これにより「-m32omf」オプションも削除されましたので、ご利用の方は注意してください。
また、一般に配布されている古いビルド済みライブラリなんかをリンクして使っている場合も要注意です。

なおOMFやPE/COFFについて説明すると長くなってしまうので省きますが、気になる方は以下を参照してください。

今回の変更により、OMFを使っていたプロジェクトは、PE-COFFへの移行が必要となります。
具体的には以下2つです。

  • ビルドプロセスで「-m32omf」オプションを使用していた場合は、それを削除する。
  • サードパーティライブラリやカスタムビルドシステムに依存している場合は、PE-COFFに対応しているかを確認し、バージョンアップ対応を行う。

古いライブラリの更新はちょっと大変かもしれませんが、PE/COFFはWindows NT系のOSに向けて作られたものなので、かれこれもう20年以上使われているようなフォーマットです。
今後のサポートを考えると移行は必要な作業かと思いますので、ぜひ取り組んでみてください。更新したら1本立派な記事になると思います!

まとめ

このリリースでは、ビットフィールドの新しいプロパティやCTFE用のメッセージ出力など、D言語のメタプログラミングをさらに強化するアップデートが多く行われました。
特に、C23に準拠する識別子のサポートが追加されたことで、日本語を含む多言語でのプログラミングが可能となります。これにより、無理に英訳しなくても日本語のまま変数名や関数名を使えるようになり、コーディングが容易になったり、プログラムの可読性が向上したりする場面が増えてくると思います。(もちろん英語にこだわることも配布などの面で良いことですが)

また、削除・エラーに関する変更もいくつか見られましたが、その中でもOMFフォーマットのサポート終了は少し影響があるかもしれません。古いPGをメンテナンスする方など、リンク時のエラーに遭遇した場合はこの変更のことを何とか思い出してみてください。

ここまで記事が長くなってしまいましたが、前回も今回も非常に大規模な改修が行われています。興味のある機能があれば、ぜひ実際に試してみてください!今後もさらに多くの機能強化が予定されていますので、引き続きD言語の進化を楽しみに待ちましょう!

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?