はじめに
D言語の公式コンパイラである dmd が更新され、最新バージョンである 2.103.0
が 2022/04/01 にリリースされました。
今回は前回からほぼ2ヶ月でのリリースで、やや小粒ながら押さえどころの多いリリースとなっているため内容ご紹介していきます。
より細かい内容は下記リンクからChangeLogをご覧ください。
- ChangeLog
また、前回である2月版のリリースまとめは以下になります。
- D言語の更新まとめ 2023年2月版(dmd 2.102.0)
変更点目次
コンパイラ変更点
- ImportC に
__check(assign-expression)
が追加されました。 - クラスに対する
alias this
は非推奨となりました。 -
-preview=dip25
がデフォルトで有効になりました。 -
export int a;
はdllimport
ではなく、dllexport
を生成するようになりました。 -
__traits(isVirtualFunction)
および__traits(getVirtualFunctions)
が非推奨になりました。
改善点
- Bugzilla 11316: デリゲート引数の型推論が欠けているケースの修正
- Bugzilla 13656: ref型の変数を宣言しようとする際のエラーメッセージを明確化
- Bugzilla 16495:
__traits(fullyQualifedName)
をstd.traits.fullyQualifiedName
の代わりに使用 - Bugzilla 20101: BetterC: CTFEのみのコンテキストでのテンプレートインスタンス化は、コード生成/ nogc / ...フェーズをスキップするべき
- Bugzilla 23558:
__traits(getModuleClasses [, モジュール名])
を追加 - Bugzilla 23597: .diファイルが
-i
オプションと互換性がない
ランタイム変更点
変更点
今回はありませんでした。
改善点
- Bugzilla 18765: [配列] 静的配列の要素リテラルでの初期化に関するドキュメント情報が不足
- Bugzilla 20997: スコープガードが
return
文の後に実行される例が欠けている - Bugzilla 22418: 文字列に関するドキュメントの誤り
- Bugzilla 22594: "Interfacing to C"のページで
intptr_t
およびuintptr_t
を含めるように更新 - Bugzilla 23612: 記事インデックスにテンプレート制約の記事が表示されない
- Bugzilla 23636:
shared
修飾子に関する仕様ドキュメントがない - Bugzilla 23730: IsExpression における
Identifier :
および== TypeCtor
の仕様を明確化
ライブラリ変更点
- 書記素(grapheme)の走査ロジックが Unicodeバージョン15 に準拠したものに更新されました。
-
std.algorithm.iteration.joiner
のメッセージがstatic assert
でより分かりやすくなりました。 -
std.algorithm.sorting.sort
のメッセージがstatic assert
でより分かりやすくなりました。
改善点
- Bugzilla 19567:
[std.stdio]
tell
のドキュメントがあまり役に立たない - Bugzilla 20397:
[std.algorithm]
nthPermutation
のドキュメント - Bugzilla 23683:
std.file.setTimes
が必要以上の権限を要求する - Bugzilla 23706: 必要でない限りPOSIXシェルパラメータをエスケープしない
DUB 変更点
-
--color
引数は、auto
、never
、always
の値を受け付けるようになりました
注目トピック
言語・コンパイラ変更点
ImportC に __check(assign-expression)
を追加
D言語には標準で組み込まれている assert
ですが、Cコードでは通常 #include <assert.h>
を用いることで利用できるようになります。
今回の機能強化により、D言語のCソースを取り込むImportC機能において、 #include <assert.h>
をしなくても使える assert
相当の機能として __check(assign-expression)
という式が追加されました。
この __check
は標準のC言語にはない機能ですので、つまりこちらは「D言語コンパイラにおける、C言語コンパイラ拡張機能」となります。(何を言っているんだ???)
ImportCの機能も含めて簡単に使ってみると以下の通りです。通常の assert
と同じ使い方になります。
int test()
{
int x = 6;
__check(x > 0); // xが0より大きいことをチェック
x--;
__check(x == 4); // xが4であることをチェック(失敗する予定)
return x;
}
module app;
import std.stdio;
import lib; // いつも通りimportする
void main()
{
const x = test();
writeln("test: ", x);
}
dmd app.d lib.c
core.exception.AssertError@lib.c(6): Assertion failure
エラーメッセージを見るとそのまま AssertError
ですので、中身は完全にD言語の assert
です。
コンパイラスイッチ -checkaction=C
を使用すると、Cの assert
マクロと同じ動作になります。
また、コンパイラスイッチに -release
が指定されると、__check
は単に無視されます。
なお、 __assert
という名前にすることも考えられたようですが、いくつかのCの .h
ファイルと競合したため避けられたようです。
これでまたひとつ ImportC の機能が使いやすくなりました。C言語のソースが手元にある方は、こちらもぜひ使ってみてください。
export int a;
は dllimport
ではなく、dllexport
を生成するようになります
ここで扱う dllimport
と dllexport
は、WindowsのDLLを含む共有ライブラリなど、外部で宣言されたシンボルの存在を示したり取り扱うための用語です。
これはD言語ではなく一般論としてですが、dllimport
と dllexport
について少し整理しておくと、以下のような使い分けをします。
-
dllimport
は、あるDLLから別のDLLや実行可能ファイルがシンボルを利用する際に使用する(外部の定義を使うだけである場合) -
dllexport
は、DLL内で定義されたシンボルを外部の実行可能ファイルやDLLに公開するために使用する(自分が定義して外部の誰かが使う場合)
これらに対してD言語では、こういった外部シンボルを扱うために export
というキーワードが用意されていました。
今回この挙動が一部修正され、export int a;
といういかにも外部公開されそうな記述で dllimport
になっていたものが、名前通り dllexport
になります。
結果として、以下のような目的別の使い分けになります。
export int a;
export void f() { }
export extern int a;
export void g();
関数宣言のところは本体の有無で変わりますが、外部のシンボルを参照するときに本体書けないことを思いだせば、それぞれ相応の振舞いになるので特に問題は起きないと思います。
元々 export int a;
と書いていた方は、 export extern int a;
と書き換える必要があります。
ちなみに、類似のキーワードで extern
を単体で指定することもできます。(ややこしい)
詳しい説明をすると長くなりそうなので、気になる方は下記リンクを参照してみてください。
extern参考
__traits(getModuleClasses [, モジュール名])
が追加されました
ChangeLogには細かい記載がありませんが、改善点のほうに書かれていた内容で少し重要そうなのでご紹介します。
こちら、現在のモジュールか指定したモジュールからクラス定義を得るための __traits
となっています。
以下のように使います。
module test;
import std.stdio;
class C { }
pragma(msg, __traits(getModuleClasses)); // tuple("test.C")
pragma(msg, __traits(getModuleClasses, std.stdio)); // tuple("std.stdio.StdioException")
第2引数がない場合は現在のモジュールのクラスを得て、第2引数にモジュールを指定すると定義されたクラスを得ることができます。
何のために追加されたかといえば Object.factory
というクラスの実行時情報型を得るための関数があるのですが、これの代替です。
Object.factory
を使おうとしたとき、非推奨のメッセージとしてこちらの __traits(getModuleClasses)
を使うように推奨される内容が表示されます。
module object;
class Object {
deprecated("use __traits(getModuleClasses) instead")
static Object factory(string classname) {
// 省略
}
}
こちらは __traits
なので静的(コンパイル時)に決まる範囲でしか使えませんが、これはこれで強力な使い方が出来そうであるので、思いついた方はぜひ使ってみてください。
ライブラリ変更点
書記素(grapheme)の走査ロジックが Unicodeバージョン15 に準拠したものに更新されました。
書記素(grapheme)とは、テキストにおいて意味を区別する最小の視覚的な単位のことです。(一方で「1文字」を表現する単位は「コードポイント」と呼ばれます)
参考 : https://ja.wikipedia.org/wiki/%E6%9B%B8%E8%A8%98%E7%B4%A0
D言語の文字列をこの書記素単位で処理する関数としては、std.uni
の graphemeStride
、byGrapheme
、および decodeGrapheme
関数などがあります。
これらは、古いUnicode規格の時代のルールを使用していましたが、今回書記素の分割ルールがUnicodeバージョン15に対応するように更新されました。
これにより、Phobosの各関数は拡張された絵文字(例:国旗や家族の絵文字)や、特定の言語で使われる修飾文字(例:アクセント記号)を認識するようになりました。この変更により、より正確にテキストを処理できるようになります。
テキスト処理の需要も伸びてきている気がしますので、こういった強化への追従は重要ですね。
書記素は便利な概念であるので個人的にも今後使っていきたいと思います。
std.algorithm.iteration.joiner
と std.algorithm.sorting.sort
のメッセージが static assert
によりわかりやすくなります
コードを読む方が早そうな話であるので、 joiner
のほうからソース抜粋します。
元々以下の定義でした。
auto joiner(RoR, Separator)(RoR r, Separator sep)
if (isInputRange!RoR && isInputRange!(ElementType!RoR)
&& isForwardRange!Separator
&& is(ElementType!Separator : ElementType!(ElementType!RoR)))
{
// 省略
}
改善後はこうなっています。
auto joiner(RoR, Separator)(RoR r, Separator sep)
{
static assert(isInputRange!RoR, "The type of RoR '", RoR.stringof
, " must be an InputRange (isInputRange!", RoR.stringof, ").");
static assert(isInputRange!(ElementType!RoR), "The ElementyType of RoR '"
, ElementType!(RoR).stringof, "' must be an InputRange "
, "(isInputRange!(ElementType!(", RoR.stringof , "))).");
static assert(isForwardRange!Separator, "The type of the Seperator '"
, Seperator.stringof, "' must be a ForwardRange (isForwardRange!("
, Seperator.stringof, ")).");
static assert(is(ElementType!Separator : ElementType!(ElementType!RoR))
, "The type of the elements of the separator range does not match "
, "the type of the elements that are joined. Separator type '"
, ElementType!(Separator).stringof, "' is not implicitly"
, "convertible to range element type '"
, ElementType!(ElementType!RoR).stringof, "' (is(ElementType!"
, Separator.stringof, " : ElementType!(ElementType!", RoR.stringof
, "))).");
// 省略
}
テンプレート関数の後についていた if
のあたりが消えて、static assert
がガッツリ増えました。
この消えた if
は「テンプレート制約(Template constraints)」と呼ばれ、テンプレートの一致性を判定するための機能となっています。
今回やっている内容は、「テンプレート制約に反したときのエラーで何に違反したかわかりづらい。問題個所をわかりやすくするため static assert
で書き直した」ということです。
ここで注意すべき点ですが、テンプレート制約を安易に static assert
に書き換えることは推奨されません。
テンプレート制約でしか実現できないこととして、「同名のテンプレートを別の定義として書いてマッチするものを選択する」というときに効いてきます。これを目的とした場合、 static assert
で書くとかなり複雑なことになります。
書き換えられる条件を考えてみると「1つしかない定義であり、今後増える見込みもない」という場合に限って書き換えを検討すると良さそうです。
標準ライブラリは特に親切なメッセージを出してもらいたいですが、使う側としても一つ参考にしていきたい内容ですね!
DUB変更点
--color 引数は、auto、never、always の値を受け付けるようになりました
Linuxツールとの互換性を保つため、より一般的に使われている auto
、never
、always
というフラグ値が使えるようになりました。
今までは automatic
, on
, off
だったのですが、こちらもこれまで通り利用できます。(ただしドキュメントには記載されません。記載されるのは auto/never/always
です)
フラグの意味はもう完全に名前の通りですが、表にしてみると以下のような対応付けになります。
元のフラグ | 更新されたフラグ名 | フラグの説明 |
---|---|---|
automatic | auto | コンソールがカラーをサポートしている場合、カラー出力を有効にします。 |
on | always | 常にカラー出力を有効にします。 |
off | never | カラー出力を無効にします。 |
非推奨または廃止される機能
今回は、非推奨が2件、廃止が1件で計3件の機能が非推奨および廃止となります。
余り影響のないものが多いですが、一部わかりづらいものもありますので一つ一つ解説していきます。
また今後の予定などは以下のページにまとまっていますので、こちらも参考としてみてください。
非推奨
- クラスに対する
alias this
は非推奨になりました。 -
__traits(isVirtualFunction)
および__traits(getVirtualFunctions)
が非推奨になりました。
削除/エラー
-
-preview=dip25
がデフォルトで有効になり、エラーを発生させる場合があります。
非推奨 : クラスに対する alias this
は非推奨になりました。
今回のリリースの中でも、やや驚きがある反響の大きいトピックです。
alias this
を簡単に説明すると、「あるオブジェクトを定義したとき、それが持つフィールドに暗黙変換を認める簡略記法」です。
struct Test {
int value;
alias value this;
}
void hoge(int n) {
writeln(n);
}
void main() {
auto obj = Test(100);
hoge(obj); // obj は Test だが、intに暗黙変換される
}
この alias this
ですが、実は struct
と union
に限定された仕様でした、というのが今回の発端です。
一方のクラスですが、クラスに対する alias this
の仕様は明確に定義されておらず、適切な変換ルールが明らかではありませんでした。
そのため、クラスで alias this
を使用すると、さまざまなエラーやクラッシュが発生することがあったようです。
そういった経緯から、このリリースでクラスに対する alias this
が明確に非推奨となります。
Deprecation: alias this for classes/interfaces is deprecated
この警告メッセージに遭遇した場合、正攻法でいけば alias this
を削除したうえで、更に発生する暗黙変換できないエラーに対処する必要があります。
import std.stdio;
void main()
{
auto t = new Test;
t.child = new Child;
t.child.name = "test";
hoge(t);
}
class Child
{
string name;
}
class Test
{
Child child;
alias child this; // ここに alias this があるので消す
}
void hoge(Child n)
{
writeln(n.name);
}
app.d(11): Error: function `app.hoge(Child n)` is not callable using argument types `(Test)`
app.d(11): cannot pass argument `t` of type `app.Test` to parameter `Child n`
暗黙変換できていたところを消すわけなので、適切にプロパティを明示すれば解決します。
void main()
{
auto t = new Test;
t.child = new Child;
t.child.name = "test";
hoge(t.child); // .child を付ける
}
なお、公式のChangeLogだと別の方法が記載されていますが、static foreach
でフィールドのプロパティに転送する定義を色々作ってしまおう、というやや強引な方法でしたのでここでは紹介しません。
類似の方法で std.typecons.Proxy
という mixin
による自動化もあるのでこちら検討していただくと良いと思います。
非推奨 : __traits(isVirtualFunction)
および __traits(getVirtualFunctions)
が非推奨になりました。
このリリースまで、D言語には __traits(isVirtualFunction)
と __traits(isVirtualMethod)
(およびそれぞれに対応する __traits(get...)
)がありました。
isVirtualFunction
は、引数が仮想関数である場合に true
を返し、それ以外の場合は false
を返します。ただし、何もオーバーライドしない final
関数に対しては true
を返します。
isVirtualMethod
は、引数が仮想関数である場合に true
を返し、それ以外の場合は false
を返します。ただし、何もオーバーライドしない final
関数に対しては false
を返します。
一見オーバライドの部分以外は同じですが、この isVirtualFunction
はD言語の仕様と矛盾した振る舞いでした。D言語では、関数をオーバーライドしない final
関数は仮想関数にならないためです。
この場合、 isVirtualMethod
は正しく false
を返しますが、 isVirtualFunction
は true
を返してしまいます。
仕様に反した振舞いということで、このリリースから、__traits(isVirtualFunction)
および __traits(getVirtualFunctions)
の両方が非推奨となります。
元の振舞いを実現したい場合は、以下のように書くことができます。
__traits(isVirtualMethod, f) || (__traits(isFinalFunction, f) && !__traits(isOverrideFunction, f))
今後は、isVirtualFunction
と getVirtualFunctions
の使用は避け、isVirtualMethod
を使用してください。
-preview=dip25
がデフォルトで有効になり、エラーを発生させる場合があります。
DIP25というD言語改善提案が正式に有効になりました。
DIPの内容確認のため、公式のソースを持ってくると以下のような内容になります。
ref int escapeRef(ref int x) { return x; }
// 以前は:
// Deprecation: returning x escapes a reference to parameter x
// perhaps annotate the parameter with return
//
// 現在は、エラーが報告されます。ただし、-revert=dip25が使用された場合を除きます。
元々DIP25はこれまで非推奨でした。これは2.092.0(2020/05/10リリース)以降非推奨となっています。
これより前のバージョンからコンパイラのバージョンを上げると突然にエラーになったように見えるかもしれません。
これを修正回避するには、関数の引数が return ref
となるように修飾を行うか、 -revert=dip25
を付けてください。
また非推奨になった当時の機能説明がありますので、詳細気になる場合は以下の記事を参考としていただければと思います。
まとめ
リリースとしては比較的小規模ですが、ImportCの強化で __check
といった独自拡張を入れられる程度には成熟してきたのかな、という印象の今リリースでした。
一方で非推奨や挙動変更もあり、やや注意が必要なバージョンと言えそうです。(ある意味小規模で良かった?)
メッセージが親切になるなど嬉しい面もありますが、 dllimport
を使っていたり、DIP25のエラーに引っかかるような場合は対処方法をしっかり押さえておく必要があります。
また今回リリースサイクルも2ヶ月サイクルに復帰しつつあるようですので、次回は順調にいけば6月です。
次のリリースでどのような話題が出てくるのか、期待して待ちたいと思います!