7
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?

More than 1 year has passed since last update.

D言語の更新まとめ 2023年4月版(dmd 2.103.0)

Posted at

はじめに

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

今回は前回からほぼ2ヶ月でのリリースで、やや小粒ながら押さえどころの多いリリースとなっているため内容ご紹介していきます。

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

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

変更点目次

コンパイラ変更点

  • 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 引数は、autoneveralways の値を受け付けるようになりました

注目トピック

言語・コンパイラ変更点

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 と同じ使い方になります。

lib.c
int test()
{
    int x = 6;
    __check(x > 0); // xが0より大きいことをチェック
    x--;
    __check(x == 4); // xが4であることをチェック(失敗する予定)
    return x;
}
app.d
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 を生成するようになります

ここで扱う dllimportdllexport は、WindowsのDLLを含む共有ライブラリなど、外部で宣言されたシンボルの存在を示したり取り扱うための用語です。

これはD言語ではなく一般論としてですが、dllimportdllexport について少し整理しておくと、以下のような使い分けをします。

  • dllimport は、あるDLLから別のDLLや実行可能ファイルがシンボルを利用する際に使用する(外部の定義を使うだけである場合)
  • dllexport は、DLL内で定義されたシンボルを外部の実行可能ファイルやDLLに公開するために使用する(自分が定義して外部の誰かが使う場合)

これらに対してD言語では、こういった外部シンボルを扱うために export というキーワードが用意されていました。

今回この挙動が一部修正され、export int a; といういかにも外部公開されそうな記述で dllimport になっていたものが、名前通り dllexport になります。

結果として、以下のような目的別の使い分けになります。

DLLを作ってシンボルを外部から使えるようにしたい(dllexportしたい)場合
export int a;
export void f() { }
外部のシンボルを読み込んで使いたい(dllimportしたい)場合
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.unigraphemeStridebyGrapheme、および decodeGrapheme 関数などがあります。
これらは、古いUnicode規格の時代のルールを使用していましたが、今回書記素の分割ルールがUnicodeバージョン15に対応するように更新されました。

これにより、Phobosの各関数は拡張された絵文字(例:国旗や家族の絵文字)や、特定の言語で使われる修飾文字(例:アクセント記号)を認識するようになりました。この変更により、より正確にテキストを処理できるようになります。

テキスト処理の需要も伸びてきている気がしますので、こういった強化への追従は重要ですね。
書記素は便利な概念であるので個人的にも今後使っていきたいと思います。

std.algorithm.iteration.joinerstd.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ツールとの互換性を保つため、より一般的に使われている autoneveralways というフラグ値が使えるようになりました。

今までは 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 ですが、実は structunion に限定された仕様でした、というのが今回の発端です。
一方のクラスですが、クラスに対する 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);
}
alias-thisを消すと出るエラーメッセージ
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`

暗黙変換できていたところを消すわけなので、適切にプロパティを明示すれば解決します。

修正版main
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 を返しますが、 isVirtualFunctiontrue を返してしまいます。

仕様に反した振舞いということで、このリリースから、__traits(isVirtualFunction) および __traits(getVirtualFunctions) の両方が非推奨となります。

元の振舞いを実現したい場合は、以下のように書くことができます。

__traits(isVirtualMethod, f) || (__traits(isFinalFunction, f) && !__traits(isOverrideFunction, f))

今後は、isVirtualFunctiongetVirtualFunctions の使用は避け、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月です。

次のリリースでどのような話題が出てくるのか、期待して待ちたいと思います!

7
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
7
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?