英語版
このページの英語版を見る

関数内の実行順序は、文によって制御される。 関数の本体は、0個以上のの連続で構成される。 実行は()()順に行われるが、特定の文は効果を遅延させる場合がある。 には値はなく、その効果のために実行される。

Statement:
    EmptyStatement
    NonEmptyStatement
    ScopeBlockStatement
EmptyStatement: ;
NoScopeNonEmptyStatement: NonEmptyStatement BlockStatement
NoScopeStatement: EmptyStatement NonEmptyStatement BlockStatement
NonEmptyOrScopeBlockStatement: NonEmptyStatement ScopeBlockStatement
NonEmptyStatement: NonEmptyStatementNoCaseNoDefault CaseStatement CaseRangeStatement DefaultStatement
NonEmptyStatementNoCaseNoDefault: LabeledStatement ExpressionStatement DeclarationStatement IfStatement WhileStatement DoStatement ForStatement ForeachStatement SwitchStatement FinalSwitchStatement ContinueStatement BreakStatement ReturnStatement GotoStatement WithStatement SynchronizedStatement TryStatement ScopeGuardStatement AsmStatement MixinStatement ForeachRangeStatement PragmaStatement ConditionalStatement StaticForeachStatement ImportDeclaration

宣言の間の文法上の曖昧性は、 宣言が優先されることで解決される。 このような文を括弧で囲むと、 文であることが明確になる。

スコープ文

ScopeStatement:
    NonEmptyStatement
    BlockStatement

ローカルシンボル用の新しいスコープが、 NonEmptyStatement またはBlockStatement

新しいスコープが導入されたとしても、 ローカルシンボルの宣言は、同じ関数内の他のローカルシンボル宣言を隠す(覆い隠す)ことはできない。

void func1(int x)
{
    int x;    // 不正、xはパラメータxを影で囲む

    int y;

    { int y; } // 不正、yはスコープのyを影で囲む

    void delegate() dg;
    dg = { int y; }; // OK、このyは同じ関数内にない

    struct S
    {
        int y;    // OK、このyはローカルではなくメンバである
    }

    { int z; }
    { int z; }  // OK、このzは他のzをシャドウしていない

    { int t; }
    { t++;   }  // 不正、tは未定義である
}
Best Practices: 関数内のローカル宣言は、 重複しないスコープ内であっても、すべて一意の名前を持つべきである。
local declarationローカル宣言

スコープ ブロック 文

ScopeBlockStatement:
    BlockStatement

スコープブロック文は、BlockStatementの新しいスコープを導入する。

ラベル付き文

文にはラベルを付けることができる。ラベルとは、 文の前に置かれる識別子である。

LabeledStatement:
    Identifier :
    Identifier : Statement

空の文も含め、あらゆる文にラベルを付けることができ、 goto文のターゲットとして使用できる。 ラベルを付けた文は、 break または continue文 ターゲットとしても使用できる。

ラベルは、ブロックの最後に続く文がなくても表示される。

ラベルは、宣言、変数、"型"などとは独立した名前空間にある。 それでも、ラベルはローカル宣言と同じ名前を持つことはできない。 ラベルの名前空間は、そのラベルが現れる関数の本体である。 ラベルの名前空間はネストしない。つまり、 ブロック文内のラベルは、そのブロックの外からアクセスできる。

ある関数内のラベルは、別の関数から参照することはできない。

ブロック文

BlockStatement:
    { }
    { StatementList }
StatementList: Statement Statement StatementList

ブロック文とは、{ } で囲まれた一連の文である。 文は()()順に実行され、 ブロックの終わりに到達するか、 または文が制御を他の場所に移すまで実行される。

式 文

ExpressionStatement:
    Expression ;

式が評価される。

(x + x) のように何の効果も持たない式は、 void型にキャストされない限り、式文としては 不正である。

int x;
x++;               // OK
x;                 // 不正
1+1;               // 不正
cast(void)(x + x); // OK
xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal

宣言文

宣言文は変数を定義し、 "型"、"テンプレート"、"関数"、"インポート"、 "条件"、"静的 foreach"、"静的 assert" を宣言する。 xml-ph-0000@deepl.internal

DeclarationStatement:
    StorageClassesopt Declaration

いくつかの宣言文:

int a;        // aをint型として宣言し、0に初期化する
struct S { }  // 構造体sを宣言する
alias myint = int;

If文

If文は、条件付きで文を実行するシンプルな機能を提供する。

IfStatement:
    if ( IfCondition ) ThenStatement
    if ( IfCondition ) ThenStatement else ElseStatement
IfCondition: Expression auto Identifier = Expression scope Identifier = Expression TypeCtors Identifier = Expression TypeCtorsopt BasicType Declarator = Expression
ThenStatement: ScopeStatement
ElseStatement: ScopeStatement

識別子変数が宣言されている場合は、それが評価される。 それ以外の場合、"式"が評価される。結果は、 opCast!bool() メソッドが定義されている場合、 ブール値がtrue の場合、ThenStatementに転送され 、それ以外の場合、ElseStatementに転送される。

ElseStatement は、最も内側のif 文に関連付けられる。ただし、ElseStatement が関連付けられていないものに限る

IfConditionのIdentifier形式が使用される場合、 その名前で変数が宣言され、"式"の値で初期化される。
  • auto Identifierが指定された場合、変数の型は と同じになる。
  • TypeCtors Identifierが指定された場合、変数は "式"の型として宣言されるが、TypeCtorsが適用される。
  • BasicType形式が指定された場合、変数の型が 通常の変数宣言と同様に宣言される 。

変数のスコープはThenStatementのみである。

import std.regex;

if (auto m = matchFirst("abcdef", "b(c)d"))
{
    writefln("[%s]", m.pre);    // [a]を表示する
    writefln("[%s]", m.post);   // [ef]を表示する
    writefln("[%s]", m[0]);     // [bcd]を表示する
    writefln("[%s]", m[1]);     // [c]を表示する
}
else
{
    writeln("no match");
    //writeln(m.post); // エラー: 未定義の識別子'm'
}
//writeln(m.pre);      // エラー: 未定義の識別子'm'
xml-ph-0000@deepl.internal xml-ph-0000@deepl.internal

While文

WhileStatement:
    while ( IfCondition ) ScopeStatement

While文は単純なループを実装する。

IfCondition が式である場合、その式が評価され、 ブール値に変換できる型でなければならない。IfConditiontrue である場合、ScopeStatementが実行される。 ScopeStatementが実行された後、式が再度評価され、 IfCondition が true である場合、ScopeStatementが再度実行される。この処理は、式が false に評価されるまで継続される。

int i = 0;
while (i < 10)
{
    foo(i);
    ++i;
}

auto 識別子が提供された場合、それは宣言され、 式の値と型に初期化される。そのスコープは 初期化された時点からScopeStatementの終了時までとなる。

TypeCtorsの識別子が指定された場合、 TypeCtorsで指定された型として宣言され、"式"の値で初期化される。 そのスコープは、初期化された時点から ScopeStatementの終了までとなる。

宣言子が指定された場合、それは宣言され、 "式"の値に初期化される。そのスコープは、初期化された時点から ScopeStatementの終了までとなる。

BreakStatementはループを終了する。

ContinueStatement は、再びIfConditionの評価に直接移行する。

Do文

DoStatement:
    do ScopeStatement  while ( Expression ) ;
internalinternal

Do while文は単純なループを実装する。

ScopeStatementが実行される。次に、Expressionが評価され、 ブール値に変換できる型でなければならない。 の場合、ループが再び繰り返される。 この処理が続く。

ScopeStatementが実行される。次に、"式"が評価され、 booleanに変換できる型でなければならない。true の場合、ループが再び繰り返される。 これは、"式"が false に評価されるまで続く。

int i = 0;
do
{
    foo(i);
} while (++i < 10);

BreakStatementはループを終了する。ContinueStatement は 再びの評価に直接移行する。

For文

For文は、初期化、テスト、およびインクリメント 節を持つループを実装する。

ForStatement:
    for ( Initialize Testopt ; Incrementopt ) ScopeStatement
Initialize: ; NoScopeNonEmptyStatement
Test: Expression
Increment: Expression

Initializeが実行される。 Testが評価され、booleanに変換できる型でなければならない。 Testtrue の場合、 ScopeStatementが実行される。実行後、 Incrementが実行される。 その後、Testが再度評価され、true の場合、 ScopeStatementが再度実行される。これは、 Testが false に評価されるまで続く。

BreakStatementはループを終了する。 ContinueStatement は 直接Increment に移行する。

ForStatementは新しいスコープを作成する。 Initializeで変数を宣言した場合、その変数のスコープは ScopeStatementまで拡張される。例えば:

for (int i = 0; i < 10; i++)
    foo(i);
は次のものと同じ意味になります。
{
    int i;
    for (i = 0; i < 10; i++)
        foo(i);
}

ScopeStatementは空の文であってはならない。

for (int i = 0; i < 10; i++)
    ;       // 不正
代わりに次のコードを使用すること。
for (int i = 0; i < 10; i++)
{
}

Initialize は ; のみでよい場合がある。 Test は省略可能であり、省略された場合は、true と評価されたものとして扱われる。

Best Practices: ForStatementを Foreach Statementまたは Forech Range Statementに置き換えることを検討する。 Foreachループは理解しやすく、エラーが発生しにくく、リファクタリングも容易である。

foreach文

foreach 文は一連の値を反復する。

AggregateForeach:
    Foreach ( ForeachTypeList ; ForeachAggregate )
ForeachStatement: AggregateForeach NoScopeNonEmptyStatement
Foreach: foreach foreach_reverse
ForeachTypeList: ForeachType ForeachType , ForeachTypeList
ForeachType: ForeachTypeAttributesopt BasicType Declarator ForeachTypeAttributesopt Identifier ForeachTypeAttributesopt alias Identifier
ForeachTypeAttributes: ForeachTypeAttribute ForeachTypeAttribute ForeachTypeAttributes
ForeachTypeAttribute: enum ref scope TypeCtor
ForeachAggregate: Expression
ForeachAggregateが評価される。 静的配列、動的配列、連想配列、 構造体、クラス、デリゲート、シーケンスのいずれかである 式として評価されなければならない。 NoScopeNonEmptyStatementが実行される。

ForeachAggregateが評価される。これは、静的配列、動的配列、連想配列、 構造体、クラス、デリゲート、またはシーケンスである式として評価されなければならない。 NoScopeNonEmptyStatementが実行され、

ForeachTypeListで宣言される変数の数は、 集約の種類によって異なる。宣言された変数は、 各反復処理の開始時に設定される。

指定されていない場合、ForeachType変数の型は、 ForeachAggregateの型から推測できる。auto有効なForeachTypeAttributeではないことに注意。 以下の2つのforeach 文は等価である。

int[] arr = [1, 2, 3];

foreach (int n; arr)
    writeln(n);

foreach (n; arr) // OK、nはintである
    writeln(n);

集約はループ不変でなければならない。 つまり、 NoScopeNonEmptyStatement

foreach の本体にBreakStatementがある場合、ループを終了する。 ContinueStatement がある場合、すぐに次の反復を開始する。

配列のforeach

集約が静的配列または動的配列の場合、 1つまたは2つの変数を宣言できる。1つの場合、その変数は "値"と呼ばれ、配列の各要素に順次設定される。変数の型を指定する場合は、 配列の要素型と互換性のある型でなければならない( 下記で説明する文字要素の特別な処理を除く)。 値変数は ref で宣言すると、配列の要素を変更できる。

2つの変数が宣言されている場合、最初の変数は「インデックス」と呼ばれ 、2番目の変数は上記のように「値」と呼ばれる。 インデックスは ref で宣言することはできない。 インデックスは、各反復処理において配列要素のインデックスに設定される。 インデックスの型は推測できる。

char[] a = ['h', 'i'];

foreach (i, char c; a)
{
    writefln("a[%d] = '%c'", i, c);
}

動的配列の場合、インデックス型size_t と互換性がある必要がある。ただし、配列の長さが静的に小さい整数型に収まることが分かっている場合は この限りではない。 静的配列では、配列の長さに一致する任意の整数型を使用できる。

foreach の場合、 配列の要素はインデックス0から始まり 配列の最後の要素まで順に繰り返される。foreach_reverse の場合、配列の要素は逆順で訪問される。

文字の配列を順に処理する

集約式が、charwchar、またはdcharの静的または動的配列である場合、 値変数の型はcharwchar 、またはdchar のいずれかになる。 このように、UTF配列は

char[] a = "\xE2\x89\xA0".dup;  // \u2260は3つのUTF-8バイトでエンコードされている

foreach (dchar c; a)
{
    writefln("a[] = %x", c); // 'a[] = 2260'と表示される
}

dchar[] b = "\u2260"d.dup;

foreach (char c; b)
{
    writef("%x, ", c);  // 'e2, 89, a0, 'と表示される
}

集約は文字列リテラルであり、charwchar 、またはdchar 配列としてアクセスできる。

foreach (char c; "ab")
{
    writefln("'%s'", c);
}
foreach (wchar w; "xy")
{
    writefln("'%s'", w);
}

これは次のように表示される。

'a'
'b'
'x'
'y'

連想配列のforeachループ

集約式が連想配列の場合、 1つまたは2つの変数を宣言できる。1つの場合、その変数は 配列の要素に順に設定されたである。変数の型が指定されている場合、 暗黙的に配列要素の型に変換されなければならない。

// 値の型はintである
int[string] userAges = ["john":30, "sue":32];

foreach (ref age; userAges)
{
    age++;
}
assert(userAges == ["john":31, "sue":33]);

2つの変数が宣言されている場合、最初の変数は「インデックス」 、2番目の数は「値」と呼ばれる。インデックスは 、連想配列の添字の型と互換性がなければならない。ref にはできず、 配列要素のインデックスとして設定される。

// indexの型は文字列、値の型はdoubleである
double[string] aa = ["pi":3.14, "e":2.72];

foreach (string s, double d; aa)
{
    writefln("aa['%s'] = %g", s, d);
}

配列の要素が反復処理される順序は、foreach では未定義である。 これが、連想配列のforeach_reverse が不正である理由である。

構造体とクラスに対するforeachループopApply

集約式が構造体またはクラスオブジェクトの場合、foreach は特別な opApplyforeach_reverse の動作は特別な opApplyReverse。 これらの関数はそれぞれ、以下のシグネチャを持つ必要がある。

OpApplyDeclaration:
    int opApply ( scope int delegate ( OpApplyParameters ) dg ) ;
OpApplyParameters: OpApplyParameter OpApplyParameter, OpApplyParameters
OpApplyParameter: ForeachTypeAttributesopt BasicType Declarator

dg の各OpApplyParameterは、ForeachStatement の 各 ForeachTypeと一致しなければならない 。 一致しない場合は、ForeachStatement がエラーの原因となる。

ForeachTypeAttribute はenum であってはならない。

ref の反復変数をサポートするには、デリゲートはref パラメータを取らなければならない。
struct S
{
    int opApply(scope int delegate(ref uint n) dg);
}
void f(S s)
{
    foreach (ref uint i; s)
        i++;
}
上記の例では、iref でない場合でも、opApply は一致する。そのため、ref デリゲートパラメータを使用することで、両方の形式がサポートされる。

opApply およびopApplyReverse 関数は複数存在することができ、 dg の各パラメータと ForeachStatement で宣言された各 ForeachTypeを照合して 1 つが選択される。

apply関数の本体は、 集約する要素を繰り返し処理し、各要素をdg デリゲートに順次呼び出す。デリゲートの戻り値によって、 繰り返し処理を中断するかどうかを決定する。

デリゲートを呼び出した結果は、ForeachStatementの ボディが、一致するBreakStatement 、ReturnStatement、または GotoStatementを実行した場合に、一致するラベルがForeachStatementの外にあるため、0以外の値になる。

例えば、2つの要素を格納するコンテナとなるクラスを考えてみよう。

class Foo
{
    uint[] array;

    int opApply(scope int delegate(ref uint) dg)
    {
        foreach (e; array)
        {
            int result = dg(e);
            if (result)
                return result;
        }
        return 0;
    }
}

void main()
{
    import std.stdio;

    Foo a = new Foo();
    a.array = [73, 82, 2, 9];

    foreach (uint u; a)
    {
        if (u < 5)
            break;

        writeln(u);
    }
}

これは次のように表示される。

73
82
dg パラメータのscope ストレージクラスは、デリゲートが opApply 関数のスコープから逃れることはないことを意味する(例:dg を グローバル変数に割り当てる)。dg がスコープから逃れることがないことが静的に保証できない場合、 クロージャはスタックではなくヒープに割り当てられる。
Best Practices: 可能であれば、 関数のデリゲートパラメータに をアノテートする。opApply scope

Important: opApply が例外をキャッチした場合、それらの例外が に渡されたデリゲートから発生していないことを確認する。ユーザーは、 の本文からスローされた例外がループを終了し、 の本文の外側に伝播することを期待する。 opApply foreach foreach

テンプレートopApply

opApply テンプレート化された関数とすることもでき、 これはForeachStatementに基づいてパラメータの型を推論する。 例:

struct S
{
    import std.traits : ParameterTypeTuple;  // イントロスペクション・テンプレート
    import std.stdio;

    int opApply(Dg)(scope Dg dg)
    if (ParameterTypeTuple!Dg.length == 2) // 2つのパラメータを持つforeach
    {
        writeln(2);
        return 0;
    }

    int opApply(Dg)(scope Dg dg)
    if (ParameterTypeTuple!Dg.length == 3) // 3つのパラメータを持つforeach
    {
        writeln(3);
        return 0;
    }
}

void main()
{
    foreach (int a, int b; S()) { }  // 最初のopApply関数を呼び出す
    foreach (int a, int b, float c; S()) { }  // 次のopApply関数を呼び出す
}

範囲を持つ構造体とクラスをForeachで処理する

集約式が構造体またはクラスオブジェクトであるが、opApply forforeach 、またはopApplyReverse forforeach_reverse が存在しない場合、 その場合は範囲のプリミティブで反復処理を行うことができる。foreach の場合、以下のプロパティおよびメソッドを定義する必要がある。

Foreach Range Properties
PropertyPurpose
.empty要素がもうない場合は true を返す
.front範囲の左端の要素を返す
範囲の各メソッド
MethodPurpose
.popFront()範囲の左端を 右に1つ移動する

意味:

foreach (e; range) { ... }

翻訳すると:

for (auto __r = range; !__r.empty; __r.popFront())
{
    auto e = __r.front;
    ...
}
同様に、xml-ph-0000@deepl.internal については、以下のプロパティと メソッドを定義する必要がある。

同様に、foreach_reverse については、以下のプロパティと メソッドを定義する必要がある。

Foreach_reverse Range Properties
PropertyPurpose
.empty要素がもうない場合は true を返す
.back範囲の右端の要素を返す
Foreach_reverse 範囲のメソッド
MethodPurpose
.popBack()範囲の右端を 左に1つ移動する

意味:

foreach_reverse (e; range) { ... }

翻訳すると:

for (auto __r = range; !__r.empty; __r.popBack())
{
    auto e = __r.back;
    ...
}

リンクされたリストの例:

struct Node
{
    int i;
    Node* next;
}

// range
struct List
{
    Node* node;

    bool empty() { return node == null; }

    ref int front() { return node.i; }

    void popFront() { node = node.next; }
}

void main()
{
    import std.stdio;
    auto l = new Node(1, new Node(2, null));
    auto r = List(l);

    foreach (e; r)
    {
        writeln(e);
    }
}

複数の要素値

front プロパティが、 変数の数と同じ長さの値シーケンスに展開される型を返す場合、複数のループ変数が許可される。 各変数は、 シーケンス内の対応する値に割り当てられる。

struct Tuple(Types...) // TypeSeqを受け取る
{
    Types items; // ValueSeq
    alias items this; // 値列に減衰する
}

// タプルである繰り返し要素を持つ無限範囲
struct TupleRange
{
    enum front = Tuple!(char, bool, int)('a', true, 2);
    enum bool empty = false;

    void popFront() {}
}

void main()
{
    // タプルのデストラクチャリング
    foreach (a, b, c; TupleRange())
    {
        assert(a == 'a');
        assert(b == true);
        assert(c == 2);
        break;
    }
    // タプル変数
    foreach (tup; TupleRange())
    {
        assert(tup[0] == 'a');
        assert(tup == TupleRange.front);
        break;
    }
}

関連情報: std.typecons.Tuple

デリゲートを繰り返し処理する

ForeachAggregateがデリゲートである場合、そのデリゲートの型署名と 動作は、opApply のものと同じである。 これにより、 同じクラスまたは構造体で、さまざまな名前のループ戦略を共存させることができる。

デリゲートは、要素をその場で生成することができる。

// 2の累乗を交互に符号を変えて反復するカスタムループの実装
// foreachループ本体はdgで渡される。
int myLoop(scope int delegate(int) dg)
{
    for (int z = 1; z < 128; z *= -2)
    {
        auto ret = dg(z);

        // ループ本体にブレークが含まれる場合、retは0以外になる。
        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 デリゲートを指定しての生成はエラーとなる。

シーケンスのforeach

集約式がシーケンスの場合、 ループ本体は静的に各要素に対して1回ずつ展開される。これは シーケンスに対する静的foreach と同じである。

反復シンボルは1つまたは2つ宣言できる。1つの場合、そのシンボルは シーケンス内の各要素の要素エイリアスとなる。

2つのシンボルが宣言されている場合、最初のシンボルはインデックス変数 であり、2番目のシンボルは要素エイリアスである。インデックスは intuintlong 、またはulong 型でなければならず、 ref 型にはできず、 各シーケンス要素のインデックスに設定される。

例:

import std.meta : AliasSeq;

void main()
{
    alias Seq = AliasSeq!(int, "literal", main);

    foreach (int i, sym; Seq)
    {
        pragma(msg, i, ": ", sym.stringof);
    }
}

出力:

0: int
1: "literal"
2: main()

例:

void fun()
{
    import std.meta : AliasSeq;

    alias values = AliasSeq!(7.4, "hi", [2,5]);

    foreach (sym; values)
    {
        pragma(msg, sym, " has type ", typeof(sym));
    }
}

出力:

7.4 has type double
hi has type string
[2, 5] has type int[]

foreach Ref Parameters

ref ForeachAggregateの要素を変更するために使用することができます。 これは、左辺値要素を公開するコンテナ、および 左辺値シーケンスで動作します。

uint[2] a = [7, 8];

foreach (ref u; a)
{
    u++;
}
foreach (u; a)
{
    writeln(u);
}
これは以下のように出力される。
8
9

ref 配列のインデックス変数には適用できない。

foreach の制限事項

集約自体は、foreach が要素を繰り返し処理している間は、サイズ変更、再割り当て、解放、 再割り当て、または破棄しては ならない。

int[] a = [1, 2, 3];
auto fun = { a ~= 4; };

foreach (int v; a)
{
    // リサイズは不特定!
    fun();
    a ~= 4;
    a.length += 10;

    // 再割り当ては未定義である!
    a.reserve(10);

    // 再割り当ては不特定である!
    a = null;
    a = [5, 6];
}
a ~= 4;   // OK
a = null; // OK
xml-ph-0000@deepl.internal
auto aa = [1: 1, 2: 2];

foreach (v; aa)
{
    aa[3] = 3; // 不特定のリサイズ
    aa.rehash; // 不特定の再割り当て
    aa = [4: 4]; // 不特定のreassign
}
aa[3] = 3; // OK
aa = null; // OK
注釈: 動的配列または連想配列のサイズ変更や再割り当ては、foreach の間でも@safe のままである。

Foreach Range Statement

foreach range文は、指定された範囲を繰り返し処理する。

RangeForeach:
    Foreach ( ForeachType ; LwrExpression .. UprExpression )
LwrExpression: Expression
UprExpression: Expression
ForeachRangeStatement: RangeForeach ScopeStatement

ForeachType は、明示的な型、または LwrExpressionUprExpression から推論された共通の型を持つ変数を宣言する。 ScopeStatement はその後n 回実行され、n は UprExpression - LwrExpression の結果である。 UprExpressionLwrExpression 以下の場合は、 ScopeStatementは実行されない。

Foreachforeach の場合、変数は LwrExpression に設定され、各反復の最後にインクリメントされる。 Foreachforeach_reverse の場合、変数は UprExpression に設定され、各反復の前にデクリメントされる。 LwrExpressionUprExpressionはそれぞれ ScopeStatementが実行される回数に関わらず、 正確に1回だけ評価される。

import std.stdio;

int foo()
{
    write("foo");
    return 10;
}

void main()
{
    foreach (i; 0 .. foo())
    {
        write(i);
    }
}
出力:
foo0123456789
emailemail

switch文

switch文は、switch式の値に応じて、case文の集合のいずれか1つに移動する。
SwitchStatement:
    switch ( IfCondition ) ScopeStatement
CaseStatement: case ArgumentList : ScopeStatementListopt
DefaultStatement: default : ScopeStatementListopt
ScopeStatementList: StatementListNoCaseNoDefault
StatementListNoCaseNoDefault: StatementNoCaseNoDefault StatementNoCaseNoDefault StatementListNoCaseNoDefault
StatementNoCaseNoDefault: EmptyStatement NonEmptyStatementNoCaseNoDefault ScopeBlockStatement

IfConditionの式が評価される。 式の型が enum の場合、 (再帰的に)EnumBaseTypeに変換される。 次に、型が整数型の場合、 は 整数型への昇格を受ける。 次に、式の型は、整数型、または charwchar 、またはdchar の静的または動的配列でなければならない。

識別子 = 接頭辞が提供された場合、その名前で変数が宣言され、 "式"の値と型に初期化される。そのスコープは、 初期化された時点からScopeStatementの終了までとなる。

Identifier に TypeCtorsおよび/または特定の型が指定された場合、それらは 変数宣言に使用され、"式" の値からの暗黙の変換によって初期化される 。

結果の値は、 "case"式のそれぞれと比較される。一致するものがあれば、 対応する"case"文に処理が移る。

ArgumentList内のcase式は、 カンマで区切られた式のリストである。 各式は、コンパイル時の値または配列、 または実行時に初期化された定数または整数型の不変の変数として評価されなければならない。 各式は、switch式の型に暗黙的に変換できなければならない。

コンパイル時の case の値はすべて異なるものでなければならない。定数または 不変の実行時変数はすべて異なる名前でなければならない。 2つの case 式が同じ値を共有している場合、 その値を持つ最初の case 文が制御を取得する。

ScopeStatementListは新しいスコープを導入する。

一致するbreak 文は、switch BlockStatementを終了する。

switch文には、DefaultStatementが必ず1つ必要である。 case式のいずれにも一致しない場合、制御は デフォルト文に転送される。

Rationale: これにより、想定されるすべてのケースが意図的に処理されていることが明確になる。 関連情報: final switch
foreach (i; 2 .. 10)
{
    bool prime;

    switch (i)
    {
        case 2, 3, 5, 7:
            prime = true;
            break;
        default:
            prime = false;
    }
    writeln(i, ": ", prime);
}

switchに関連するcase文およびdefault文は、 ブロック文の中にネストすることができる。必ずしも最外ブロック内に置く必要はない。 例えば、これは許可される。

switch (i)
{
    case 1:
    {
        case 2:
    }
    i++;
    break;
    default:
}

Implementation Note: コンパイラのコード生成機能は、 case 文が使用頻度順にソートされていると想定することがあり、最も使用頻度の高い ものが最初に、最も使用頻度の低いものが最後に表示される。これは、 プログラムの正確性に関しては無関係であるが、 パフォーマンスには影響する。

ケース 範囲 文

CaseRangeStatement:
    case FirstExp : .. case LastExp : ScopeStatementListopt
FirstExp: AssignExpression
LastExp: AssignExpression

CaseRangeStatementは、 FirstExpからLastExp までの範囲のケース文を列挙する省略形である。

case 1: .. case 3:

これは次のものと同じである。

case 1, 2, 3:

暗黙のフォールスルーなし

スコープステートメントリストは、空であるか、または ContinueStatement 、BreakStatementReturnStatementGotoStatementThrowExpression 、またはassert(0) 式で終了しなければならない。ただし、これが最後の場合を除く。

switch (i)
{
    case 1:
        message ~= "one";
        // エラー: 暗黙のフォールスルー
    case 2:
        // 有効: ボディは空である
    default:
        message ~= "unknown";
}

goto case; 明示的なフォールスルーに使用できる。

string message;

foreach (i; 1..5)
{
    switch (i)
    {
        default:    // 有効: 'throw'で終わる
            throw new Exception("unknown number");

        case 3:     // 有効: 'break'で終わる('switch'からだけ抜け出す)
            message ~= "three";
            break;

        case 4:     // 有効: 'continue'で終わる(ループを続ける)
            message ~= "four";
            continue; // カンマを付けない

        case 1:     // 有効: 'goto'で終わる(明示的に次のケースにフォールスルーする。)
            message ~= ">";
            goto case;

        case 2:     // 有効: これはswitch文の最後のケースである。
            message ~= "one or two";
    }
    message ~= ", ";
}
writeln(message);
また、特定のケースまたはデフォルトのケース文へのジャンプもサポートしている 。

goto特定のケースまたはデフォルトのケース文へのジャンプもサポートしている。

文字列スイッチ

文字列は switch 式で使用できる。 例:

string name;
...
switch (name)
{
    case "fred":
    case "sally":
        ...
}

コマンドラインスイッチ処理のようなアプリケーションでは、 これにより、よりわかりやすいコードとなり、エラーが発生しにくくなる。charwchardchar の文字列が許可される。

emailemail

最終的なswitch文

FinalSwitchStatement:
    final switch ( IfCondition ) ScopeStatement

最終的なswitch文は、switch文とほぼ同じであるが、 ただし、次の点が異なる。

Implementation Defined: 式の値が CaseRangeStatements のいずれにも一致しない場合、 その診断はコンパイル時または実行時のいずれに行われるかに関わらず
valuevalue

Continue 文

ContinueStatement:
    continue Identifieropt ;

continue 最も内側の囲みループ文の現在の反復を中止 し、次の反復を開始する。 囲みループが 文である場合、 そのfor インクリメント節が実行される。

string[] words = ["OK", "just", "longer", "words", "now"];

foreach (w; words)
{
    if (w.length < 4)
        continue; // writeelnをスキップする

    writeln(w);
}

出力:

just
longer
words

continue の後にIdentifier が続く場合、Identifier は 囲みループ文のラベルでなければならず、 そのループの次の反復が実行される。 そのような文がない場合はエラーとなる。

outer:
foreach (item; list)
{
    // 3回試す
    foreach (i; 0 .. 3)
    {
        if (item.buy())
            continue outer; // 次の項目にスキップする

        log("attempt failed");
    }
}

間に挟まれた finally節は実行され、 同期オブジェクトは解放されます。

Note: finally 節がfinally節の を実行した場合、 継続対象には到達しない。throw

ブレークステートメント

BreakStatement:
    break Identifieropt ;

break 最も内側の囲みループまたは switch その次の文から実行を再開する。

const n = 55;

// nの最小の因数を見つける
foreach (i; 2 .. n)
{
    writeln("Trying: ", i);
    if (n % i == 0)
    {
        writeln("smallest factor is ", i);
        break; // 探すのをやめる
    }
}
writeln("finished");

出力:

Trying: 2
Trying: 3
Trying: 4
Trying: 5
smallest factor is 5
finished

break の後にIdentifier が続く場合、Identifierは 囲みループのラベルまたはswitch 文でなければならず、その文が終了する。そのような文がない場合はエラーとなる。

// shopが閉じるまでメッセージを繰り返し表示する
outer:
while (true)
{
    foreach (msg; messages)
    {
        if (shop.isClosed())
            break outer; // whileループを終了する

        display(msg);
    }
}
display("opens at 9am");

間に挟まれた finally節は実行され、 同期オブジェクトは解放されます。

Note: finally 節がfinally節の を実行した場合、 ブレークポイントは到達しない。throw

Return文

ReturnStatement:
    return Expressionopt ;
現在の関数を終了し、 戻り値を返す。

return 現在の関数を終了し、その 戻り値を返す。

関数が void ではない戻り値の型を指定している場合は、"式" が必須となる。 式は暗黙的に関数の戻り値の型に変換される。

関数が void を返す型を指定している場合、void 型式は許可される。 式は評価されるが、何も返されない。これはジェネリックプログラミングで有用である。

関数が実際に返す前に、scope ストレージ期間を持つオブジェクトはすべて破棄され、finally 節がすべて実行され、scope(exit) 文がすべて実行され、scope(success) 文がすべて実行され、 そして、すべての enclosing synchronization オブジェクトが解放される。

この関数は、finally 節を囲む return、goto、またはスローによってfinally 節が終了する場合、戻りません。

out の事後条件がある場合、 その事後条件は "式"が評価された後、関数が実際に 値を返す前に実行される。

int foo(int x)
{
    return x + 3;
}

Goto Statement

GotoStatement:
    goto Identifier ;
    goto default ;
    goto case ;
    goto case Expression ;

goto 識別子とラベル付けされた文に転送される。

    if (foo)
        goto L1;
    x = 3;
L1:
    x++;

2番目の形式であるgoto default; は、それを囲むSwitchStatementの最も内側のDefaultStatementに転送される。

3番目の形式、goto case; は、 最も内側のSwitchStatementの 次のCaseStatementに 転送される。

4番目の形式、goto case Expression; は、 一致する式を持つ 最も内側のSwitchStatementの CaseStatementに 転送される。

switch (x)
{
    case 3:
        goto case;
    case 4:
        goto default;
    case 5:
        goto case 4;
    default:
        x = 4;
        break;
}

間に挟まれた finally 節はすべて実行され、 間に挟まれた同期ミュートックスが解放される。

初期化をスキップするためにGotoStatementを使用することは、 不正である。

With文

with 文は、同じオブジェクトへの繰り返し参照を簡素化する方法である。

WithStatement:
    with ( Expression ) ScopeStatement
    with ( Symbol ) ScopeStatement
    with ( TemplateInstance ) ScopeStatement
式"が以下のいずれかに評価される場合:
with ボディ内では、参照されたオブジェクトがまず 識別子シンボルについて検索される。
enum E { A, B }

void test(E e)
{
    with (e)       // switch文に影響を与える
    switch (e)
    {
        case A:    // E.Aは必要ない
        case B:
        default:
            break;
    }
}

以下、identexpression の型に属する場合は、WithStatement:

with (expression)
{
    ...
    ident;
}
意味的には以下と同等である。
(auto ref tmp)
{
    ...
    tmp.ident;
}(expression);

式は一度だけ評価され、コピーはされないことに注意。 with文は、this またはsuper が参照するものを変更しない。

SymbolがスコープまたはTemplateInstanceの場合、 シンボルを検索する際に、対応するスコープが検索される。 例えば:

struct Foo
{
    alias Y = int;
}
...
Y y;        // エラー、Yは未定義
with (Foo)
{
    Y y;    // Foo.Y yと同じ;
}

with オブジェクトシンボルを使用して、ローカルシンボルを同じ識別子で覆い隠すことは 許可されていない。 これは、オブジェクト宣言に新しいメンバーが追加された際に、with文が誤って壊れてしまうリスクを低減するためである 。

xml-ph-0000@deepl.internal
struct S
{
    float x;
}

void main()
{
    int x;
    S s;
    with (s)
    {
        x++;  // エラー、int xの宣言が影になる
    }
}

ネストされたWithStatementsでは、最も内側のスコープが優先される。 最も内側のスコープでシンボルが解決できない場合、 スコープ階層を順次上に遡って解決が試みられる。

import std.stdio;

struct Foo
{
    void f() { writeln("Foo.f"); }
}

struct Bar
{
    void f() { writeln("Bar.f"); }
}

struct Baz
{
    // f()は実装されていない
}

void f()
{
    writeln("f");
}

void main()
{
    Foo foo;
    Bar bar;
    Baz baz;

    f();               // "f"を表示する

    with(foo)
    {
        f();           // "Foo.f"を表示する

        with(bar)
        {
            f();       // "Bar.f"を表示する

            with(baz)
            {
                f();   // "Bar.f"を表示する。`Baz`は`f()`を実装していない
                       // resolutionは`with(bar)`のスコープに転送される
            }
        }
        with(baz)
        {
            f();       // "Foo.f"と表示する。`Baz`は`f()`を実装していない
                       // resolutionは`with(foo)`のスコープに転送される
        }
    }
    with(baz)
    {
        f();           // "f"を表示する。`Baz`は`f()`を実装していない
                       // resolutionは`main`のスコープに転送される
                       // `f()`は`main`のスコープに実装されていないので、
                       // resolutionはモジュールのスコープに転送される。
    }
}

同期文

synchronized文は、 複数のスレッド間のアクセスを同期させるために、mutexによるロックとアンロックで文を囲む。

SynchronizedStatement:
    synchronized ScopeStatement
    synchronized ( Expression ) ScopeStatement

式を伴わないsynchronized 文では、 一度に1つのスレッドのみが グローバル・ミューテックスを作成し、同期文ごとに1つずつ使用する。 異なる同期文には、異なるグローバル・ミューテックスが使用される。

式"がある場合、それは"オブジェクト"または"インターフェースのインスタンス"のどちらかに評価されなければならない オブジェクトまたはインターフェースのインスタンスに評価されなければならず、 その場合、 そのインターフェースを実装したオブジェクトのインスタンスにキャストされる。 使用されるミューテックスは、そのオブジェクトのインスタンスに固有のものであり、 そのインスタンスを参照するすべての同期文で共有される。 同期文に到達した時点でオブジェクトのミューテックスがすでにロックされている場合、 そのミューテックスが他のコードによってアンロックされるまで、すべてのスレッドがブロックされる。

void work();

void f(Object o)
{
    synchronized (o) work();
}

void g(Object o)
{
    synchronized (o) work();
}

fg が異なるスレッドから呼び出された場合でも、同じ 引数を指定すると、work の呼び出しは同時に実行することはできない。 synchronized 文の(o) 部分が、1つまたは両方の関数で削除された場合、 両方のwork 呼び出しは同時に実行できる可能性がある 。なぜなら、それらは異なるミューテックスによって保護されるからだ。

ScopeStatementが例外、goto、returnで終了した場合でも、同期は解除される。

これは標準的なクリティカルセクションを実装する。

synchronized文は再帰的ロックをサポートしている。つまり、 synchronizedで囲まれた関数は、再帰的に自身を呼び出すことが許可されており、 その動作は期待通りのものとなる。つまり、 再帰の回数だけ、ミューテックスがロックされ、ロックが解除される。

同期クラスも参照。

Try文

例外処理は、try-catch-finally文を使用して実行される。

TryStatement:
    try ScopeStatement Catches
    try ScopeStatement Catches FinallyStatement
    try ScopeStatement FinallyStatement
Catches: Catch Catch Catches
Catch: catch ( CatchParameter ) NoScopeNonEmptyStatement
CatchParameter: BasicType Identifieropt
FinallyStatement: finally NoScopeNonEmptyStatement

CatchParameterは型 T の変数 v を宣言する。ここで、T は Throwable または Throwable から派生した型である。T がスロー式と同じ型またはスロー式の基底クラスである場合、v はスロー式によって初期化される。 例外オブジェクトが型 T または T から派生した型である場合、catch 節が実行される

型 T が与えられ、変数 v が指定されていない場合、catch 節は 依然として実行される。

CatchParameter型T1が、その後に続くCatch型T2を隠蔽する場合はエラーとなる。 すなわち、 T1がT2と同じ型である場合、またはT2の基底クラスである場合はエラーとなる。

FinallyStatementはtry ScopeStatementがgoto、break、continue、return、例外、またはフォールスルーで終了した場合でも、 常に実行される。

FinallyStatementで例外が発生し、 元の例外がキャッチされる前にキャッチされなかった場合、 Throwableの 次のメンバーを介して前の例外に連結される。 ほとんどの他のプログラミング言語とは対照的に、新しい 例外が元の例外に置き換わるわけではないことに注意。 代わりに、後続の 例外は最初の例外によって引き起こされた「二次的な被害」とみなされる。 元の例外はキャッチされなければならず、その結果、 連鎖全体が捕捉される。 xml-ph-0000@deepl.internal

クラスから派生したスローオブジェクトは Errorクラスから派生したスローされたオブジェクトは、異なる方法で処理される。 通常の連鎖メカニズムをバイパスし、最初のError をキャッチしない限り、連鎖をキャッチできない。 Error には、その後の例外のリストに加えて、 バイパスが発生した場合に元の例外(連鎖の頭)を指すポインタも含まれている。 これにより、例外の履歴全体が保持される。

import std.stdio;

int main()
{
    try
    {
        try
        {
            throw new Exception("first");
        }
        finally
        {
            writeln("finally");
            throw new Exception("second");
        }
    }
    catch (Exception e)
    {
        writefln("catch %s", e.msg);
    }
    writeln("done");
    return 0;
}
以下のように表示されます。
finally
catch first
done

FinallyStatementは、goto、break、continue、またはreturnでは終了しない。 また、gotoで開始することもできない。

FinallyStatementには 、Catchを含めることはできない。 この制限は、将来のバージョンで緩和される可能性がある。 xml-ph-0000@deepl.internal

スコープガード文

ScopeGuardStatement:
    scope ( exit ) NonEmptyOrScopeBlockStatement
    scope ( success ) NonEmptyOrScopeBlockStatement
    scope ( failure ) NonEmptyOrScopeBlockStatement

ScopeGuardStatementは、現在のスコープの終了時に、NonEmptyOrScopeBlockStatement 実行する。 ScopeGuardStatementが現れた時点ではなく、。scope(exit) は、スコープが正常に終了した場合、または例外の解消により終了した場合にNonEmptyOrScopeBlockStatement 実行する。scope(failure) は、 例外の解消によりスコープが終了した場合に、NonEmptyOrScopeBlockStatementを実行する。scope(success) は、スコープが正常に終了した場合に、NonEmptyOrScopeBlockStatementを実行する。

スコープ内に複数のScopeGuardStatementが存在する場合、それらは 出現する()()の逆順で実行される。 スコープの終了時に任意のスコープ・インスタンスが破棄される場合、 それらの破棄は出現する()()の逆順でScopeGuardStatementと交互に実行される 。

write("1");
{
    write("2");
    scope(exit) write("3");
    scope(exit) write("4");
    write("5");
}
writeln();
は次のように書いている。
12543
{
    scope(exit) write("1");
    scope(success) write("2");
    scope(exit) write("3");
    scope(success) write("4");
}
writeln();
writes:
4321
struct Foo
{
    this(string s) { write(s); }
    ~this() { write("1"); }
}

try
{
    scope(exit) write("2");
    scope(success) write("3");
    Foo f = Foo("0");
    scope(failure) write("4");
    throw new Exception("msg");
    scope(exit) write("5");
    scope(success) write("6");
    scope(failure) write("7");
}
catch (Exception e)
{
}
writeln();
0412
scope(exit) またはscope(success) 文は、 "スロー"、"goto"、"break"、"continue"、"return"で終了してはならない。また、"goto"で開始してはならない。scope(failure)文は、"return"で終了してはならない。
import std.stdio;

int foo()
{
    scope(exit) writeln("Inside foo()");
    return bar();
}

int bar()
{
    writeln("Inside bar()");
    return 0;
}

int main()
{
    foo();
    return 0;
}
は次のように書く。
Inside bar()
Inside foo()

C++ クラスオブジェクトの取得

多くのプラットフォームでは、C++ クラスオブジェクトのキャッチングがサポートされている。 C++ オブジェクトと D オブジェクトのキャッチングを両方とも同じTryStatement 内で行うことはできない。 Catch から抜けた時点で、C++ オブジェクトのデストラクタが実行され、そのオブジェクトに使用されていたストレージが解放される。 C++ オブジェクトは、@safe コードではキャッチングできない。

アセンブリ言語文

インラインアセンブラはasm文でサポートされている。

AsmStatement:
    asm FunctionAttributesopt { AsmInstructionListopt }
AsmInstructionList: AsmInstruction ; AsmInstruction ; AsmInstructionList

asm文により、アセンブリ言語命令を直接使用することが可能になる。 これにより、外部のアセンブラを使用することなく、特別なCPU機能に直接アクセスすることが容易になる。 Dコンパイラは、 関数呼び出し規約、スタックのセットアップなどを処理する。

命令のフォーマットは、もちろん、 ターゲットCPUのネイティブ命令セットに大きく依存しており、 処理系定義である。 しかし、フォーマットは以下の規約に従う

これらの規則は、Dソースコードが構文解析や意味解析とは独立してトークン化できるようにするために存在する。

例えば、Intel Pentiumの場合:

int x = 3;
asm
{
    mov EAX,x; // xをロードしてレジスタEAXに入れる
}

インラインアセンブラを使用してハードウェアに直接アクセスできる。

int gethardware()
{
    asm
    {
        mov EAX, dword ptr 0x1234;
    }
}

DからCへのトランスレータなど、一部のD実装では、インラインアセンブラは 意味をなさないため、実装する必要はない。このことを説明するために、バージョン文を使用できる。

version (D_InlineAsm_X86)
{
    asm
    {
        ...
    }
}
else
{
    /* ... 何らかの回避策 ... */
}

意味的に連続するAsmStatementsの間には、 コンパイラによって他の命令(レジスタの保存や復元など)を挿入してはならない。

プラグマ文

PragmaStatementを参照

ミックスイン文

MixinStatement:
    mixin ( ArgumentList ) ;

ArgumentList内の 各AssignExpressionは コンパイル時に評価され、結果は文字列として表現可能でなければならない。 結果の文字列は結合されて1つの文字列が形成される。 文字列のテキスト内容は、有効なStatementListとしてコンパイル可能でなければならず、 そのようにコンパイルされる。

import std.stdio;

void main()
{
    int i = 0;
    mixin("
        int x = 3;
        for (; i < 3; i++)
            writeln(x + i, i);
        ");    // OK

    enum s = "int y;";
    mixin(s);  // OK
    y = 4;     // OK、ミックスインがyを宣言した

    string t = "y = 3;";
    //mixin(t);  // エラー、tはコンパイル時に評価できない
    //mixin("y =") 4; // エラー、文字列は完全な文でなければならない

    mixin("y =" ~ "4;");  // OK
    mixin("y =", 2+2, ";");  // OK
}