8
0

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年2月版(dmd 2.102.0)

Posted at

はじめに

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

今回は前回からほぼ2ヶ月でのリリースで、スケジュールを優先してやや小粒ながらも便利な強化を含んでいるのでご紹介していきます。

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

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

変更点目次

コンパイラ変更点

  • extern (D) @system な関数定義の衝突が見逃されるケースが非推奨になりました。
  • オーバーロード集合に対して __traits(getAttributes) を呼び出す機能は非推奨になりました。
  • 空でない for 文において、副作用のないインクリメント句は非推奨になりました。
  • scope 宣言された配列変数に割り当てられた配列リテラルはスタックに確保されるようになりました。
  • static assert は複数のメッセージ引数をサポートするようになりました。
  • -preview=systemVariables が追加されました。

改善点

  • Bugzilla 9848: 期待されていない型宣言がある場合の診断が改善されました。
  • Bugzilla 15368: foreach 内の auto キーワードに対するエラーメッセージを改善しました。
  • Bugzilla 21338: テンプレートのオーバーロード解決に失敗した場合のエラーメッセージを混乱させる場合がある。
  • Bugzilla 22306: scope の配列変数はスタックに割り当てる必要があります。
  • Bugzilla 23410: ImportC: バイナリ定数は許可されません。
  • Bugzilla 23424: テンプレートのインスタンス化がどのオーバーロードにもマッチしない場合のエラーを改善しました。
  • Bugzilla 23458: OverDeclaration または OverloadSet がマッチしない場合、テンプレート候補がリストアップされない。
  • Bugzilla 23466: -verrors=context は補足メッセージに同じコンテキストを繰り返してはいけません。
  • Bugzilla 23480: 空でない ForStatement の Increment 句は副作用を必要とするはずです。
  • Bugzilla 23552: 関数 x はどの関数もオーバーライドしないが、実際にはオーバーライドする。
  • Bugzilla 23566: ImportC: __PRETTY_FUNCTION__ が未定義です。

ランタイム変更点

変更点

  • Throwable.TraceInfo@nogc で生成されるようになりました。

改善点

  • Bugzilla 20650: GCが呼ぶファイナライザの循環エラーメッセージを改善しました
  • Bugzilla 23332: core.sync.conditionnotify メソッドが @nogc で動作するようになりました

ライブラリ変更点

  • log 関数族に対し、単精度および倍精度の実装が追加されました。
  • Unicode Propertyにおける誤った値(COther)が修正されました。
  • Unicodeテーブル生成がPhobosに組み込まれ、version 15 に更新されました。
  • std.typecons.Uniquestruct のデストラクタを呼び出すようになりました。

改善点

  • Bugzilla 19737: ドキュメントにある [std.experimental.allocator] のリンクテーブルがシンボル内に見つからない
  • Bugzilla 23453: 汎用の iotaforward range であるべきです

DUB 変更点

  • バイナリ出力は中央キャッシュに置かれるようになります。
  • DUB APIの破壊的変更: Package.metadataCache の setter と getter が削除されました。

インストーラー変更点

  • バンドルされている VisualD のパッケージが更新されました。
  • Windows 64bitでは、32bitのDMDより64bitを優先するようになりました。

注目トピック

言語・コンパイラ変更点

強化 : scope 宣言された配列変数に割り当てられた配列リテラルはスタックに確保されるようになりました。

-preview=dip1000 フラグが有効な場合に限り、配列変数が scope 変数かつ配列リテラルにより初期化されると、その変数がスタックに割り当てされるようになりました。

コードとしては以下のようなイメージです。

void main() @nogc
{
    int[] arr1 = [1, 2, 3, 4]; // ヒープに割り当てられて参照している状態
    scope int[] arr2 = [1, 2, 3, 4]; // スタックに割り当てられる

    scope arr2 = [1, 2, 3, 4]; // scopeで型推論が効くのでこれでもOK
}

なお、これと同じことをやろうと思うとひと手間必要で以下のようなコーディングとなります。
今後はより簡単に書けるので scope で良さそうな所は積極的に付けていくのが良さそうです。

void main() @nogc
{
    int[3] buffer = [10, 20, 30];
    int[] arr = buffer[];
}

現時点では以下の注意点および制限があります。将来的に緩和変更される可能性があります。

  • 変数は明示的に scope として宣言されている必要があります。
  • レガシーコードでメモリ破壊が起こり得るため、-preview=dip1000 を指定する必要があります。
  • @system@trusted のコードでは、コンパイラは scope 変数がエスケープされるか検証しないことに注意してください。
  • 配列リテラルで変数を初期化する必要があります。これ以外の配列リテラルを代入した場合、従来通りヒープおよびGCを使用します。

また注意点に以下の項目もあったのですが、ちょっと意味が分かりませんでした…

The array elements may not have a destructor

強化 : static assert は複数のメッセージ引数をサポートするようになりました。

今回の便利強化の1つです。

コンパイル時に様々な判定を行ってエラーにする static assert のメッセージに複数の値を渡して文字列化および連結を適当にやってくれるようになります。

利用例
enum e = 3;
static assert(false, "a = ", e);
出力
file.d(2): Error: static assert:  a = 3

今までは std.format を読み込んでコンパイル時にフォーマットするなどの手間がありました。
これによってかなりコーディングが楽になり、static assert のメッセージが優しくなる事が期待できますね。ぜひ使っていきましょう。

強化 : -preview=systemVariables が追加されました。

メモリ安全性に関する強化で、「@system 変数」という概念が追加される比較的重要な強化です。
D言語強化提案の中では DIP1035 として管理されていますので、指定可能な場所や挙動の詳細はそちらで確認ください。

さて、今までD言語におけるメモリ安全性のチェックというと、 @safe を付けた関数で行う操作は安全だ、という程度でしたが、これにもまだ厳密には穴があります。
具体的には、 @safe な関数とそうでない関数でグローバル変数を経由して情報受け渡しがある場合、です。

@safe がない、つまり安全でない関数は操作したメモリを壊す可能性があります。
これが仮にグローバル変数を操作して壊すような場合、その結果に依存する @safe 関数も同様に安全とは言い切れない、という状況が起こります。
(Rustでも unsafe 内でグローバル変数を操作するときに同様の指摘があり、2023年2月現在も未解決です)

今回この問題の対策として、-preview=systemVariables というコンパイラフラグが追加されます。
以下のようなサンプルコードになりますが、フラグを指定しない場合は警告、指定した場合はエラー、という動作になります。

@system int* p; // @systemを追加しておく。これ自体は特に何もしない

struct S
{
    @system int i; // @systemを追加しておく。これ自体は特に何もしない
}

void main() @safe
{
    int x = *p; // 警告/エラー @safeな範囲から@systemな変数を扱えない

    S s;
    s.i = 0; // 警告/エラー フィールドでも同様に@systemであれば触れない
}
警告例(-preview=systemVariablesなし)
test.d(12): Deprecation: cannot access `@system` variable `p` in @safe code
test.d(15): Deprecation: cannot access `@system` field `S.i` in `@safe` code
test.d(17): Deprecation: cannot access `@system` variable `p` in @safe code
test.d(18): Deprecation: cannot access `@system` field `S.i` in `@safe` code
test.d(18): Deprecation: cannot access `@system` field `S.i` in `@safe` code
エラー例(-preview=systemVariablesあり)
test.d(12): Error: cannot access `@system` variable `p` in @safe code
test.d(15): Error: cannot access `@system` field `S.i` in `@safe` code
test.d(17): Error: cannot access `@system` variable `p` in @safe code
test.d(18): Error: cannot access `@system` field `S.i` in `@safe` code

構造体の一部フィールドに付けることができるので、C言語との連携で受け渡しになる箇所など、安全とは言い切れないメモリを差す場所では積極的に指定すると良さそうです。

ランタイム変更点

強化 : Throwable.TraceInfo@nogc で生成されるようになりました。

スタックトレースの生成周りで @nogc 指定の拡充が行われました。
結果として、今までスタックトレースが見えなかった範囲(主にGCの処理の中)でもスタックトレースが見えるようになる、という強化になります。

そもそもGC内部のエラーに遭遇することが無い方が圧倒的に多いと思われること、またスタックトレースを見てあれこれしないといけない状況が前提なので、そこまで嬉しいものではないかもしれません。

スタックトレースの情報といっても噛み砕けばメッセージ類は文字列型だったりするので、このあたりがGCを使ってメモリを確保することになります。
一方で、GC内部で起きるエラー(たとえば InvalidMemoryOperationError)にもスタックトレースを付けたいわけですが、この時はGC内部ですのでGC依存というのが再帰的な関係になるため困難です。(解放中に起きたエラーでまたメモリ確保する必要が出たりするため)

今回 @nogc という属性がついたことでGC依存が明示的に取り除かれました。
結果として、GC内部で起きたエラーでもスタックトレースが見れるようになったりする、というのが今回の強化です。

なお、内部的には mallocfree といったメモリ操作を独自で行うようになります。

改善 : core.sync.conditionnotify メソッドが @nogc で動作するようになりました

なりました。
今回のターゲットは core.sync.condition.Condition というクラスですが、Mutex による排他的な同期に対し、割り込めるスキマを作るような操作を実現するちょっと特殊なクラスです。
とはいえ属性が付くことにより、変化としては単純に扱える範囲が広くなるので嬉しい限りですね。

ちなみにこちら、内部的には throw new SyncError(...); といった一文がGC依存を起こしていた状況でした。
これをGCなしで構築するため、ランタイムが持つ static なTLS領域に例外オブジェクトを構築して投げる、という方法により解決しています。

ランタイムの中では staticError というテンプレート関数が形式化されており、グローバル変数を使ってややハック気味なコードで実現しているので興味があれば見てみてください。

ライブラリ変更点

強化 : log 関数族に対し、単精度および倍精度の実装が追加されました。

されました。

元々あったのは real log(real) など、D言語で扱える最大精度の浮動小数点である real 型についてのみです。

拡大変換は暗黙的に行えるので個人的には気になっていなかったのですが、今回特化型のロジックが用意されたことで幾分高速化などが期待できると思います。

追加されたのは以下です。

  • log
  • log10
  • log1p
  • log2
  • logb

強化 : Unicode Propertyにおける誤った値(COther)が修正されました。

Unicode Technical Report #44 の不具合対応として定義値が変更されました。
変化したのは、std.uniunicode.cunicode.Other です。(大文字小文字はそのままです)

これはUnicode propertyと呼ばれる文字種類のような情報を取るものです。
正直に言って、これを扱う方であれば理解されたこともあるかと思いますので詳細は割愛させていただきます。

ちなみに元に戻すためには、以下のような書き換えが必要となります。

@property auto loadPropertyOriginal(string name)() pure
{
    import std.uni : unicode;

    static if (name == "C" || name == "c" || name == "other" || name == "Other")
    {
        auto target = unicode.Co;
        target |= unicode.Lo;
        target |= unicode.No;
        target |= unicode.So;
        target |= unicode.Po;
        return target;
    }
    else
        return unicode.opDispatch!name;
}

強化 : Unicodeテーブル生成がPhobosに組み込まれ、version 15 に更新されました。

されました。
Unicode version 15の詳細は触れませんが、公式ブログによると日本語を含む4489文字が追加されているそうです。
http://blog.unicode.org/2022/09/announcing-unicode-standard-version-150.html

他のプログラミング言語でもこういったことはあると思いますが、ちょっと珍しいと言えば珍しいですね。

強化 : std.typecons.Uniquestruct のデストラクタを呼び出すようになりました。

Unique が何かといえば、ある型に対し、ムーブセマンティクスに基づく単一の所有権管理を行うヘルパーテンプレートです。(なんか煙に巻いた感がぬぐえない説明…)

要するに、あるオブジェクトがあちこちにコピーされることを防いだり、誰が破棄を行うのか明示するために用いられます。

今回の変更により以下のようなコードが動作するようになりました。以前は assert の時点で呼ばれているかはGCが動くかどうかなので不定です。

static int i; // カウンター

struct S
{
    ~this()
    {
        i++; // デストラクタでインクリメント
    }
}

{
    Unique!S u = new S;
    // このスコープを抜けるときに S のデストラクタが呼ばれたらカウンターがインクリメントされる
}

assert(i == 1);

Unique!S の初期化式を見ると、見るからに new を使っています。
つまり構造体 S の実体はGCで管理され、言ってしまえばヒープにあるので、元々その破棄タイミングはGC依存になっていた、という話です。

今後は破棄のタイミングが明確になりますし、その目的から想定される動作としても分かりやすいかと思いますので適切な変更だと思われます。

DUB変更点

強化 : バイナリ出力は中央キャッシュに置かれるようになります。

あまり意識する機会はないですが、主に依存パッケージのビルドキャッシュを置く位置が変更になりました。

環境毎に以下の場所になります。

Linux
$HOME/.dub/cache/build/$BASE_PACKAGE_NAME/$PACKAGE_VERSION/[+$SUB_PACKAGE_NAME]

Windows
%APPDATA%/cache/build/$BASE_PACKAGE_NAME/$PACKAGE_VERSION/[+$SUB_PACKAGE_NAME]
※訳注 : 試す限り、正しくは %LOCALAPPDATA%/dub/cache/$BASE_PACKAGE_NAME/$PACKAGE_VERSION/[+$SUB_PACKAGE_NAME] です

ちなみにこれを気にすべき状況というと、主にCIのビルドキャッシュやDockerのビルドキャッシュです。

特に作るアプリに対して依存関係というのは変わりづらいので、それらすべてを毎回ビルドするのはある意味無駄とも言えます。(フルビルドしたいこともあるのでそれはそれ)

GitHub ActionsでもDocker(BuildKit)でも、実行をまたいでディスクを共有するような機能があり、キャッシュすることで物によっては数分単位でビルド時間削減が望めます。
その時にこれらのパスを使いまわすことでビルドキャッシュが最大限に使える、ということです。ぜひ覚えておきましょう。

インストーラー変更点

強化 : バンドルされている VisualD のパッケージが更新されました。

Visual StudioでD言語の開発を行う VisualD のパッケージが 1.3.1 に更新されました。
前回からの更新差分としてはメモリリークやWinSDKを使った際のビルド修正とのことです。

元々がVisual StudioでC++を書くことに慣れている方向けだと思いますが、色々な機能がありますので気になる方がいれば試してみてください。

強化 : Windows 64bitでは、32bitのDMDより64bitを優先するようになりました。

Windowsインストーラーで出てくる Add to PATH という項目があり、そこで追加する環境変数が64bit版のDMDの位置を優先するようになった、という変更です。

いわゆるビルドツールとしての dub を使っている方であれば気にすることはないのですが、無条件に32bit版で環境構築していて、かつ素の dmd を叩いて使っていてるような場合は注意が必要です。

自分が使っている dmd が何ビット版か確認するには、 dmd --version と入力して、先頭に出てくる DMD64 D Compiler ... という文字列を見ると分かります。

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

今回は、非推奨が3件、廃止が1件で計4件の機能が非推奨および廃止となります。

余り影響のないものが多いですが、一部わかりづらいものもありますので一つ一つ解説していきます。

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

非推奨

  • extern (D) @system な関数定義の衝突が見逃されるケースが非推奨になりました。
  • オーバーロード集合に対して __traits(getAttributes) を呼び出す機能は非推奨になりました。
  • 空でない for 文において、副作用のないインクリメント句は非推奨になりました。

削除/エラー

  • DUB APIの破壊的変更: Package.metadataCache の setter と getter が削除されました。

非推奨 : extern (D) @system な関数定義の衝突が見逃されるケースが非推奨になりました。

非推奨になったということですが、現実にこれで困る方はいないだろうという変更となっています。

状況としては、extern (D)(Dリンケージ)において @system を付けたかどうかで関数がオーバーロードされたかのようになってしまい、一見コンパイルが通っていた、というものです。

void foo() {}
void foo() @system {} // エラーにならない

当然ですが、こう書いている関数を実際に呼び出そうとしてもどちらか解決できずに曖昧であるため、現在動いているコードに対して実害のある変更ではありません。
またWindowsの場合は同様のコードに対してリンクエラーが起きますので実行ファイルが作られません。

参考までに非推奨の警告文を出しておくと以下のようなメッセージになります。

test.d(4): Deprecation: function `test.foo` cannot overload `extern(D)` function at test.d(3)

ではいつ困るのかというと、リンク等しないで使う場合、たとえばドキュメントコメントを使ってリファレンスを生成するような場合です。

このときオーバーロードしていても処理は正常に終了するので、2つ関数があると定義が2つあるように出力される恐れがあります。
というわけで、一度生成されたドキュメントに間違ったオーバーロードがないか確認いただくと良いと思います。

なお、こちら将来的に 2.105.0 でエラーになる予定です。

非推奨 : オーバーロード集合に対して __traits(getAttributes) を呼び出す機能は非推奨になりました。

D言語の関数定義は、「同じ名前を持つもの」でオーバーロード集合を形成します。
このオーバーロード集合がある時、関数名を書くと、それがすなわちオーバーロード集合を指す状態になります。

そこで問題となっていたのが下記のようなコードです。

module test;

@("gigi")
void fun() {}
@("mimi")
void fun(int) {}

void main()
{
    static foreach(attr; __traits(getAttributes, fun))
        pragma(msg, attr);

状況解説すると、__traits(getAttributes) は関数の属性を取るものですが、fun のある定義には "gigi" という文字列の属性が付き、別の定義には "mimi" という属性が付いています。
ここで __traits(getAttributes) の動作はどうなるかというと、オーバーロード集合に対応していなかったため、テキスト上最初に行われた宣言がピックアップされ "gigi" のみが取れていました。

動作として分かりづらいとのことで、今後オーバーロード集合を形成している場合に getAttributes を行うと警告が出るようになります。

test.d(8): Deprecation: `__traits(getAttributes)` may only be used for individual functions, not overload sets such as: `fun`
test.d(8):        the result of `__traits(getOverloads)` may be used to select the desired function to extract attributes from

ではオーバーロード集合がある場合でも1つ1つの定義から属性を取りたい場合はどうするかというと、 __traits(getOverloads) と組み合わせると良いそうです。
コードイメージとしては以下のようになります。

module test;

@("gigi")
void fun() {}
@("mimi")
void fun(int) {}

void main()
{
    static foreach (t; __traits(getOverloads, test, "fun"))
        static foreach(attr; __traits(getAttributes, t))
            pragma(msg, attr);

非推奨 : 空でない for 文において、副作用のないインクリメント句は非推奨になりました。

分かりづらい動作を招くとして、文法上許されていた記述が一部非推奨になりました。

コードの例としては3つあります。

// j を副作用なしでただ評価するだけ(要するに ++ を忘れている)
int j;
for (;; j) {...}

// ポインタを進めた後、不要な参照外しがある
for (ubyte* sp;; *sp++) {...}

// 最初の句はブロック構文になるが、
// 最後の句はブロック構文ではなく関数リテラルになる(関数が定義されるだけでj++もd++も実行されない)
for({j = 2; int d = 3;}; j + d < 7; {j++; d++;}) {...}

これに該当すると以下のような警告文が表示されます。

test.d(7): Deprecation: `j` has no effect
test.d(10): Deprecation: `*sp++` has no effect
test.d(14): Deprecation: `__lambda4` has no effect

3つ目だけちょっと気付きづらいかもしれませんが、行番号も表示されるので、has no effect で判断すれば大丈夫かと思います。
もしこの件で警告が出る場合はロジック見直すべきかもしれないのでご注意ください。

削除 : Package.metadataCache の setter と getter が削除されました。

非常に限定的な状況ですが、dub をライブラリとしてお使いの方に影響があるかもしれない変更です。

今回、Package クラスの metadataCache という関数が2つ(getter/setterに相当)削除されました。

この2つの関数は元々、Generator と呼ばれる種類のクラスにメタデータキャッシュへのアクセスを提供するために使用されていました。
公開APIとして現れているものの、あくまで内部実装向けに用意されたものであり、キャッシュとしてのフォーマットも安定していませんでした。

今回機能強化で中央集権的なビルドキャッシュが導入されていますが、上記関数を利用している場合に適切な移行パスを提供する方法がないとの指摘があり、結果として安全のため削除される必要がある、と判断されました。

これらの関数は一般に使用されていないはずですが、もし使用例があれば dub のレポジトリでissueを報告してください、とのことです。

(試しにGitHubで検索しても見つからないので多分安全だと思います)

まとめ

リリースとしては小粒にまとまった今回ですが、中身を振り返るとかなり様々な分野に手が入っています。

特に @system 変数などの言語的な強化、log 関連の関数を拡充、@nogc の活用に向けたTraceInfoの改善、DUBの中央キャッシュなどが目玉になります。

数値計算からマイクロサービスまで非常に色々な用途で使われる言語ですので、それらのニーズに対して着々と進んでいるということかと思います。

次回以降のリリースでは、名前付き引数などの強化も予定されているそうです。
今回の内容を踏まえて新しい機能を活用しつつ、今後に期待しながらプログラミングしていただければと思います。

8
0
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
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?