はじめに
D言語の公式コンパイラである dmd が更新され、最新バージョンである 2.108.0
が 2024/04/01 にリリースされました。
今回は前回からほぼ2ヶ月でのリリースで、久しぶりの言語的な大幅機能強化と言えそうな内容がいくつか盛り込まれています。まだまだ成長中ですので今後をお楽しみに!
さて、記事にするのはかなり遅れてしまいましたが、今回も一通りの内容ご紹介していきます。
より細かい内容は下記リンクからChangeLogをご覧ください。
- ChangeLog
また、前回である2月版のリリースまとめは以下になります。
- D言語の更新まとめ 2023年2月版(dmd 2.107.0)
変更点目次
言語・コンパイラ変更点
変更点
-
object.d
のTypeInfo_Class
に.nameSig
フィールドが追加されました。 -
__FILE__
のようなキーワードは、常に呼び出し元で評価されるようになりました。 - 16進数の文字列が整数配列に変換されるようになりました。
- Interpolated Expression Sequences(式の補間シーケンス)がサポートされました。
- 関数に名前付き引数が実装され、ドキュメント化されました。
改善点
- Bugzilla 3543: クラスやインターフェースに対して共通型を推定できない三項演算子の問題を修正しました。
- Bugzilla 18919:
__FILE__
および__LINE__
がデフォルト引数式内で使用された際に正しく動作するようにしました。(訳注:変更点 2と同じ内容です) - Bugzilla 24316: CTFE でポインタを通じてイミュータブル変数にアクセスできるようにしました。
- Bugzilla 24397: C プリプロセッサの関数ライクマクロをサポートしました。
ランタイム変更点
改善点
- Bugzilla 15504:
core.demangle
が通常の制御フローで例外処理を使用する問題を修正しました。 - Bugzilla 19702:
DECLARE_HANDLE
の使用をやめる。
ライブラリ変更点
変更点
-
isForwardRange
、isBidirectionalRange
、およびisRandomAccessRange
がオプションの要素型を取れるようになりました。 -
std.uni
が Unicode 15.0.0 から 15.1.0 にアップグレードされました。
改善点
- Bugzilla 24318:
Nullable
がコピー不可能なオブジェクトをサポートするようになりました。 - Bugzilla 24382:
std.range.only
で、要素を代入可能にしました。
Dub変更点
変更点
-
fetch
コマンドが複数の引数、再帰的フェッチ、およびプロジェクト認識をサポートするようになりました。
dlang.org変更点
改善点
- Bugzilla 24313: ダウンロードページでGithubのナイトリービルドを参照するべきです。
- Bugzilla 24331:
@nogc
とGC.disable()
の混同を防ぐための改善を行いました。
注目トピック
言語・コンパイラ変更点
object.d
の TypeInfo_Class
に .nameSig
フィールドが追加されました。
クラスの完全修飾名に対する16バイトのMD5署名として、.nameSig
フィールドが追加されました。
これは2つのクラスが等しいかどうかを比較するために使用され、ポインタを比較するのではなく、名前の文字列比較を行うフォールバックとして使用されます。
この変更により、クラス間の比較がより高速になります。
これを直接使うことはほとんどなさそうですが、この変更によって druntime
と phobos
の両方で変更に対応するため再コンパイルが必要です。
これに伴ってさらに、関連するライブラリも再コンパイルが必要です。
つまり、古いライブラリをリンクして利用しているユーザーは、新しいコンパイラで依存関係を再コンパイルする必要があります。
コンパイル前に元のファイルは念のためバックアップして、一度やっておくと良いと思われます。
__FILE__
のようなキーワードは、常に呼び出し元で評価されるようになりました。
__FILE__
などの予約語を書いたとき、いつどこで評価されるのか少し厳密になりました。
今回の変更では、これらのキーワードが「初期化子」(引数の既定値指定など)で用いられている場合、呼び出し元の関数のソースコードの場所で評価されるようになりました。
ちなみに標準ライブラリの中では、Exception
のコンストラクタなどで使われていますが、こちらは特に変わりありません。
つまり、以前は呼び出し元で評価されない複雑なケースがあったということです。今回呼び出し元で評価されるようになった例を見てみましょう。
void func1(const(char)* file = __FILE__.ptr, size_t line = __LINE__)
{
// これで呼び出し元の関数のファイル名が出力されるようになりました。
// 以前は、`func1` 自体のファイル名が出力されていました。
printf("%s:%zd\n", file, line);
}
struct Loc
{
string file;
size_t line;
}
void func2(Loc loc = Loc(__FILE__, __LINE__))
{
// `loc` 変数には呼び出し元のファイルと行番号が格納されます。
// 以前は、`func2` 自身の場所が格納されていました。
writeln(loc.file, ":", loc.line);
}
Loc defaultLoc(string file = __FILE__, size_t line = __LINE__)
{
return Loc(file, line);
}
void func3(Loc loc = defaultLoc)
{
// `loc` 変数には、`func3` の呼び出し元のファイルと行番号が格納されます。
// 以前は、`func3` や `defaultLoc` の場所が格納されていました。
writeln(loc.file, ":", loc.line);
}
(func1
は普通にありそうな…?)
なお、このような予約語は、__FILE__
, __FILE_FULL_PATH__
, __MODULE__
, __LINE__
, __FUNCTION__
, __PRETTY_FUNCTION__
があります。
この中では __PRETTY_FUNCTION__
あたりが結構珍しいと思いますが、これは関数シグネチャ(int test.main(string[] args)
など)が得られるものです。
こちらいずれもエラーメッセージの改善などに便利だと思われるので、ライブラリ作者の方やログを残すときにぜひ使ってみてください。
ちなみにC++にも似たような機能があるのでそちらをお使いの方も頑張ってみてください。
16進数の文字列が整数配列に変換されるようになりました。
タイトルからちょっとわかりづらいですが、以下のようなコードが書けるようになりました。
immutable uint[] data = x"AABBCCDD"; // 左辺の要素は uint、右辺は16進数文字列、1文字が1要素に対応付けされる
static assert(data[0] == 0xAABBCCDD); // ソースの見た目と同じ16進数リテラルとして扱われる
これまでの仕様では、short[]
、int[]
、long[]
といった型を16進のデータで簡単に初期化するための方法がありませんでした。
これは、CTFE(コンパイル時関数実行)で配列をキャストすることは許可されていないためです。
現在では、16進数文字列がすべての整数配列に暗黙的に変換できるようになりました。この時のバイトオーダーはビッグエンディアンが使用されており、整数リテラルの記述方法と一致しています。
ちなみにこれは任意の文字列が16進数文字列として扱われるわけではなく、x
という接頭辞のついた文字列リテラルのみが暗黙変換されます。
string t = x"41424344";
immutable uint[] b = t; // 暗黙的変換はNG
writefln!"%x"(b[0]);
immutable uint[] c = cast(immutable uint[])t; // 明示的変換はOK
writefln!"%x"(c[0]);
Error: cannot implicitly convert expression `t` of type `string` to `immutable(uint[])`
また、文字の接尾辞を組み合わせて、要素サイズが2バイトか4バイトかを明示的に設定することも可能です。
immutable ushort[] f = x"80 3F"w; // xがあれば実はスペースもOK
static assert(f[0] == 0x803F);
immutable ubyte[] g = x"80 35"w; // エラー: サイズの不一致
以前は、各バイトに1つまたは3つのゼロをパディングしていましたが、これは実際には目的にかなっていなかったとの理由で変更されました。もし文字列のバイト長がターゲットの要素サイズの倍数でない場合はエラーが発生します。(Issue 24363参照)
immutable ushort[] e = x"AABBCC"w; // エラー: 3バイトは `ushort.sizeof` の倍数ではありません
バイナリデータを扱う場合は便利な機能です。記述が楽になるとGithub Copilotなど使う場合でも書かせるのが楽になりそうですね。
ちなみにUnicodeの更新対応の項目にも関係してくるのですが、コンパイル時間の短縮にも一役買うことがあるようです。地味ですが、D言語にとってはとても大切な強化ですね。
Interpolated Expression Sequences(IES、式の補間シーケンス)のサポートを追加
今回の目玉機能の1つです。
個人的に聞き馴染みがないですが、Interpolated Expression Sequence、通称IES
、「式の補間シーケンス」という機能が追加されました。(なんか違和感あるので適切な翻訳があれば教えてください)
これはいわゆる「文字列補間」や「埋め込み文字列」と知られている、文字列の中に変数や式を文字列として簡単に展開する機能に似た仕組みです。
この強化によって文字列補完ができるようになりましたが、厳密にはただの文字列補完機能よりもう少し汎用性の高い機能が提供されています。
ちょっと使用例を見てみましょう。
今回、3つの形式のリテラルが追加されました。いわゆる文字列の中に $()
で囲まれた式を埋め込む形式です。
i"Content $(a + 4)"
i`Content $(a + 4)`
iq{Content $(a + 4)}
接頭辞の i
がついているのが特徴です。
こう見るとPowerShellとほぼ同じですね。よく見るJavaScriptなどは {}
ですが、そうでない理由は元々の文字列リテラル(3番目の例)との混同を避けるためかと思われます。
上記の例はすべて同じ結果になり、例えば std.stdio
の writeln
のような他の関数に渡すことができます。
int a = 6;
writeln(i"Content $(a + 4)"); // "Content 10" と出力されます
さて、ここで気になる点があります。こちらのリテラルの 型 は何でしょうか?文字列なのでしょうか?
int a = 100;
auto s = i"Content $(a + 4)";
writeln(typeof(s).stringof); // 型
writeln(s); // 評価結果
実行してみると…
(InterpolationHeader, InterpolatedExpression!"n + 100", int, InterpolationFooter)
Content 104
えぇ…???
こちら InterpolationHeader
、InterpolatedExpression
、int
、InterpolationFooter
という型が ()
の中に出力されました。
これは、IES がタプルを返すことを示しています。要するに、Tuple!(InterpolationHeader, InterpolatedExpression!"n + 100", int, InterpolationFooter)
という 型 です。IESの結果は、NOT文字列であることに注意です。あくまでも「シーケンス」なので名称に注意です。
writeln
などの関数は、このIESを受け取って適切に処理することで文字列のように扱っています。
では実際埋め込んだ文字列が欲しいときはどうするか?というと std.conv
の text
関数を使います。
import std.conv : text;
int a = 100;
string s = i"Content $(a + 4)".text(); // UFCSで後ろに .text() を追加
ちなみに、ここで出てくる InterpolationHeader
や InterpolatedExpression
などのシーケンスは、core.interpolation
モジュールで定義された新しい型です。
InterpolatedExpression
が元の式情報を文字列として保持している点が特徴的です。HeaderとFooterはマーカーで、将来的には構文解析などを含めた高度な応用も可能でしょう。
IESは様々な利用法が考えられますが、このモジュールのドキュメントやサンプルは以下のリポジトリを参照してください。
こちら DIP1036 として2020年に提出されましたが、その後長らく議論されたり2通りの実装が存在したり、結構色々あったようです。何とか4年越しにリリースということで今後活用していきたいですね。
関数の名前付き引数が実装され、ドキュメント化されました
こちらも今回の目玉機能の1つです。DIP1030として提案されていたいわゆる「名前付き引数」が(一部)実装されました!とはいえ実用範囲では十分です!
名前付き引数は、関数を呼び出す際の引数に対して引数名を明示することで、主に明確さの向上、引数順序の柔軟性を向上させることができる機能です。(文法の見直し含めこの規模の機能が後付けできるのはすごい)
というわけで、何はともあれ簡単なサンプルコードです。
import std;
auto status = spawnShell("dub upgrade", workDir: "../projects/hoge").wait(); // stdin や stdout などの引数を飛ばして workDir だけ指定することができる!stderrだけ指定してもOK!
auto newVal = clamp(a, lower: 0, upper: 100); // a を 0 から 100 の範囲に収めることが一目でわかる。lower/upperが先など順番を変えてもOK!
C#など、他の言語と同じような文法ですね。個人的に気になる点は特になさそうです。
個人的に std.process
の spawnShell
が一番使用頻度が多いかなと思います。workDir
や入出力だけ指定することができるのはかなり便利ではないでしょうか。
さて、これだけでまぁ使えそうな気はするわけですが、仕様の面からもう少し補足があります。
元々名前付き引数は、規定値のある「デフォルト引数」とセットで語られがちです。デフォルト引数をたくさん用意して、そのうち1つだけ指定する、といった用途が他の言語では一般的なためです。
それはその通りなのですが、D言語ではそれ以外にも「デフォルト引数の位置」に関係してきます。さらに言えば、関数定義が柔軟になりました。
これまではデフォルト引数といえば引数の最後にしか置けないという制約がありましたが、今回の実装によってデフォルト引数は引数リストの途中に置けるようになったからです。
void createWindow(bool fullScreen = false, int width, int height, string title); // 最初にいきなりデフォルト引数がある
void main()
{
createWindow(title: "Skynet", width: 1280, height: 720); // 他の引数名を明示したのでデフォルト引数が省略されたと分かる(!?)
}
また、名前付き引数は、構造体や共用体のリテラルにも使用可能です。共用体は、最初のフィールド以外のフィールドを設定して初期化できるようになりました。
union U
{
float asFloat;
uint asInt;
}
auto u0 = U(1.0); // `asFloat` フィールドが設定されます
auto u1 = U(asInt: 0x3F800000); // 以前は不可能でした
なお、通常の関数や構造体リテラルに対する名前付き引数の実装は、dmd 2.103 から存在していますが、これまではドキュメント化されていませんでした。
ドキュメント化された内容、関連する仕様ページは以下の通りです。
さて、冒頭で「一部実装」と前置きしましたが、これは 関数引数 に対する DIP1030 を実装した状況であり、名前付き テンプレート引数 はまだ実装されていないためです。
またDIPでは触れられていない実装の詳細、例えば名前付き引数がタプルとどのように相互作用するかなど、まだ解決すべき点もあります。
この議論については、Named Arguments Status Update を参照してください。
spawnShell
など以外に便利に使える関数にどのようなものがあるか、ぜひぜひ試してSNSなどで情報共有してもらえればと思います。
その他注意ですが、名前付き引数が前提になることで、今後シグネチャとして引数の名前や順序が重要になってきます。くれぐれも今後は 引数名の変更は慎重に 行ってくださいね!
ライブラリ変更点
isForwardRange
, isBidirectionalRange
, および isRandomAccessRange
にオプションの要素型が追加されました
dmd 2.106.0 で行われた 類似の変更 が、isForwardRange
、isBidirectionalRange
、および isRandomAccessRange
にも追加されました。
isForwardRange
, isBidirectionalRange
, isRandomAccessRange
はそれぞれ、Rangeの特性をチェックするテンプレートです。ライブラリ作者の方にはお馴染みの機能かもしれません。
これまでと同様に、これらのテンプレートに第2の型引数が渡された場合、範囲の要素型が指定された型に「修飾子変換」が可能かどうかがチェックされます。
この追加チェックが成功するとテンプレートは true
を返します。
使用例は以下の通りです。
// 完全一致
static assert( isForwardRange!(int[], int));
// 修飾子変換による一致
static assert( isBidirectionalRange!(int[], const(int)));
// 一致しない場合
static assert(!isRandomAccessRange!(int[], string));
ちなみに 修飾子変換 (Qualifier conversion) は、型修飾子(const
など)を伴う型間での互換性や暗黙変換についての仕様です。
変換可否はこちらの仕様に書かれている表がわかりやすいので参照してください。
std.uni
が Unicode 15.0.0 から 15.1.0 にアップグレードされました
2023年9月12日にリリースされたUnicodeの更新に追従し、関連するモジュールとして std.uni
が更新されました。
詳細はこちら: https://www.unicode.org/versions/Unicode15.1.0/
ざっくりUnicodeの更新内容ですが、627文字が追加、総文字数は149,813文字。主な差分は、中国国家規格GB 18030の更新に対応するために緊急で必要とされたCJK統合漢字(中国語、日本語、韓国語の漢字)とのことです。
日本語も含まれますが、中国語を扱う場合は特に注意が必要そうですね。
ChangeLogからの抜粋ですが、以下のようなコードで変更点を確認できます。
import std;
void main()
{
const alphaCount = iota(0, dchar.max).filter!(std.uni.isAlpha).walkLength;
writeln(alphaCount);
// 以前: 137765
// 現在: 138387
// 622 個の新しい `dchar` が `isAlpha` に対して `true` を返すようになりました
}
なお、内部のUnicodeテーブル(std/internal/unicode_tables.d
)では配列リテラルの代わりに16進数文字列を使用するように変更されており、インポートがより高速化されています。
速度向上の度合いはコンピュータや D コンパイラに依存しますが、std.string
や std.uni
をインポートする際、30~100ミリ秒ほど短縮される可能性があるそうです。
Dub変更点
fetch
コマンドが複数の引数、再帰的フェッチ、およびプロジェクト認識をサポート
dub fetch
はDUB Packagesとして公開されているライブラリをダウンロードするためのコマンドです。
これは元々ビルド時間の短縮のために事前ダウンロードとビルドを行うコマンドとして用意されています。(特に最近はDockerなどでビルドするとなると、キャッシュの有無で顕著ビルド時間が変わるため重要度は増してきています。
以前は、dub fetch
は単一のパッケージしかフェッチできませんでした。
今回のリリースでは、複数のパッケージをサポートするようになり、以下のようなコマンドが可能になりました。
$ dub fetch vibe-d@0.9.0 vibe-d@0.9.1 vibe-d@0.9.2
また引数なしで dub fetch
を呼び出すと、これまではエラーが発生していましたが、現在ではDUBプロジェクトとして依存関係が見つかった場合、それらの依存関係を自動的にフェッチしようと試みるようになりました。この機能を「Project-aware」(プロジェクト認識)と呼ぶそうです。
最後に、パッケージをフェッチする際に、そのパッケージの依存関係もすべてフェッチするのが便利な場合があります。
これがプロジェクトでは自動的に行われ、直接フェッチの場合でも再帰的にフェッチできるようになりました。
$ dub fetch --recursive vibe-d@0.9.0 vibe-d@0.9.1
ちなみに私はこのためにツールを作って公開していましたが、これにてお役御免の様相です。お疲れさまでしたっ…!!
まとめ
今回のリリースではIESや名前付き引数など、言語の強化がメインで久しぶりの大規模アップデートでしたね。
IESはまだまだ深掘りできる部分が多そうですし、名前付き引数も今後のライブラリ設計に大きな影響を与えそうです。誰か使ってみた感想記事をお願いします!
また、Unicodeの更新は定期的に行われますが、こういった追従は言語の健全性を保つ上で大変重要だと思います。
この辺他の言語でどうしているのか気になりますね。誰か調べてほしい…(しばらく前にも同じことを言った気がするし沼な気がする)
まとめを書いてて気づきましたが、今回のリリースでは非推奨化や機能の削除・エラーはありませんでした。(書き忘れではないので念のため)
リリースから記事の投稿まで随分と日が開いてしまいましたが、今後もD言語の情報発信は頑張っていきたいと思います。
今後のリリースも期待していきましょう!