The OpenD Programming Language

Statements

 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

pragma, PragmaStatement

version, ConditionalStatement

version, StaticForeachStatement

module, ImportDeclaration

Any ambiguities in the grammar between Statements and declaration, Declarations are resolved by the declarations taking precedence. Wrapping such a statement in parentheses will disambiguate it in favor of being a Statement.

Scope Statements

 ScopeStatement:
    NonEmptyStatement

BlockStatement

A new scope for local symbols is introduced for the NonEmptyStatement or BlockStatement.

Even though a new scope is introduced, local symbol declarations cannot shadow (hide) other local symbol declarations in the same function.

void func1(int x)
{
    int x;    // illegal, x shadows parameter x

    int y;

    { int y; } // illegal, y shadows enclosing scope's y

    void delegate() dg;
    dg = { int y; }; // ok, this y is not in the same function

    struct S
    {
        int y;    // ok, this y is a member, not a local
    }

    { int z; }
    { int z; }  // ok, this z is not shadowing the other z

    { int t; }
    { t++;   }  // illegal, t is undefined
}
Local declarations within a function should all have unique names, even if they are in non-overlapping scopes.

Scope Block Statements

 ScopeBlockStatement:
    BlockStatement

A scope block statement introduces a new scope for the BlockStatement.

Labeled Statements

Statements can be labeled. A label is an identifier that precedes a statement.

 LabeledStatement:
    Identifier :
    Identifier : Statement

Any statement can be labeled, including empty statements, and so can serve as the target of a goto statement. Labeled statements can also serve as the target of a break or continue statement.

A label can appear without a following statement at the end of a block.

Labels are in a name space independent of declarations, variables, types, etc. Even so, labels cannot have the same name as local declarations. The label name space is the body of the function they appear in. Label name spaces do not nest, i.e. a label inside a block statement is accessible from outside that block.

Labels in one function cannot be referenced from another function.

Block Statement

 BlockStatement:
    { }
    { StatementList }

StatementList: Statement

Statement StatementList

A block statement is a sequence of statements enclosed by { }. The statements are executed in lexical order, until the end of the block is reached or a statement transfers control elsewhere.

Expression Statement

 ExpressionStatement:
    expression, Expression ;

The expression is evaluated.

Expressions that have no effect, like (x + x), are illegal as expression statements unless they are cast to void.

int x;
x++;               // ok
x;                 // illegal
1+1;               // illegal
cast(void)(x + x); // ok

Declaration Statement

Declaration statements define variables, and declare types, templates, functions, imports, conditionals, static foreaches, and static asserts.

 DeclarationStatement:
    declaration, StorageClassesopt declaration, Declaration

Some declaration statements:

int a;        // declare a as type int and initialize it to 0
struct S { }  // declare struct s
alias myint = int;

If Statement

If statements provide simple conditional execution of statements.

 IfStatement:
    if ( IfCondition ) ThenStatement
    if ( IfCondition ) ThenStatement else ElseStatement

IfCondition: expression, Expression

auto Identifier = expression, Expression scope Identifier = expression, Expression type, TypeCtors Identifier = expression, Expression type, TypeCtorsopt type, BasicType declaration, Declarator = expression, Expression

ThenStatement: ScopeStatement

ElseStatement: ScopeStatement

If there is a declared <em>Identifier</em> variable, it is evaluated. Otherwise, <em>Expression</em> is evaluated. The result is converted to a boolean, using opCast!bool() if the method is defined. If the boolean is true, the ThenStatement is transferred to, otherwise the ElseStatement is transferred to.

The ElseStatement is associated with the innermost if statement which does not already have an associated ElseStatement.

When an Identifier form of <em>IfCondition</em> is used, a variable is declared with that name and initialized to the value of the <em>Expression</em>.
  • If auto Identifier is provided, the type of the variable is the same as <em>Expression</em>.
  • If TypeCtors Identifier is provided, the variable is declared to be the type of <em>Expression</em> but with TypeCtors applied.
  • If the BasicType form is provided, it declares the type of the variable as it would for a normal variable declaration.

The scope of the variable is the <em>ThenStatement</em> only.

import std.regex;

if (auto m = matchFirst("abcdef", "b(c)d"))
{
    writefln("[%s]", m.pre);    // prints [a]
    writefln("[%s]", m.post);   // prints [ef]
    writefln("[%s]", m[0]);     // prints [bcd]
    writefln("[%s]", m[1]);     // prints [c]
}
else
{
    writeln("no match");
    //writeln(m.post); // Error: undefined identifier 'm'
}
//writeln(m.pre);      // Error: undefined identifier 'm'

While Statement

 WhileStatement:
    while ( IfCondition ) ScopeStatement

A While Statement implements a simple loop.

If the IfCondition is an <em>Expression</em>, it is evaluated and must have a type that can be converted to a boolean. If it's true the <em>ScopeStatement</em> is executed. After the <em>ScopeStatement</em> is executed, the <em>Expression</em> is evaluated again, and if true the <em>ScopeStatement</em> is executed again. This continues until the <em>Expression</em> evaluates to false.

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

If an auto Identifier is provided, it is declared and initialized to the value and type of the <em>Expression</em>. Its scope extends from when it is initialized to the end of the <em>ScopeStatement</em>.

If a TypeCtors Identifier is provided, it is declared to be of the type specified by TypeCtors and is initialized with the value of the <em>Expression</em>. Its scope extends from when it is initialized to the end of the <em>ScopeStatement</em>.

If a Declarator is provided, it is declared and initialized to the value of the <em>Expression</em>. Its scope extends from when it is initialized to the end of the <em>ScopeStatement</em>.

A BreakStatement will exit the loop.

A ContinueStatement will transfer directly to evaluating IfCondition again.

Do Statement

 DoStatement:
    do ScopeStatement  while ( expression, Expression ) ;

Do while statements implement simple loops.

<em>ScopeStatement</em> is executed. Then <em>Expression</em> is evaluated and must have a type that can be converted to a boolean. If it's true the loop is iterated again. This continues until the <em>Expression</em> evaluates to false.

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

A BreakStatement will exit the loop. A ContinueStatement will transfer directly to evaluating <em>Expression</em> again.

For Statement

For statements implement loops with initialization, test, and increment clauses.

 ForStatement:
    for ( Initialize Testopt ; Incrementopt ) ScopeStatement

Initialize: ; NoScopeNonEmptyStatement

Test: expression, Expression

Increment: expression, Expression

Initialize is executed. Test is evaluated and must have a type that can be converted to a boolean. If <em>Test</em> is true the <em>ScopeStatement</em> is executed. After execution, Increment is executed. Then Test is evaluated again, and if true the <em>ScopeStatement</em> is executed again. This continues until the Test evaluates to false.

A BreakStatement will exit the loop. A ContinueStatement will transfer directly to the Increment.

A ForStatement creates a new scope. If Initialize declares a variable, that variable's scope extends through <em>ScopeStatement</em>. For example:

for (int i = 0; i &lt; 10; i++)
    foo(i);

is equivalent to:

{
    int i;
    for (i = 0; i &lt; 10; i++)
        foo(i);
}

<em>ScopeStatement</em> cannot be an empty statement:

for (int i = 0; i &lt; 10; i++)
    ;       // illegal

Use instead:

for (int i = 0; i &lt; 10; i++)
{
}

Initialize may be just ;. Test may be omitted, and if so, it is treated as if it evaluated to true.

Consider replacing ForStatements with Foreach Statements or Foreach Range Statements. Foreach loops are easier to understand, less prone to error, and easier to refactor.

Foreach Statement

A foreach statement iterates a series of values.

 AggregateForeach:
    Foreach ( ForeachTypeList ; ForeachAggregate )

ForeachStatement: AggregateForeach NoScopeNonEmptyStatement

Foreach: foreach foreach_reverse

ForeachTypeList: ForeachType

ForeachType , ForeachTypeList

ForeachType: ForeachTypeAttributesopt type, BasicType declaration, Declarator ForeachTypeAttributesopt Identifier ForeachTypeAttributesopt alias Identifier

ForeachTypeAttributes: ForeachTypeAttribute

ForeachTypeAttribute ForeachTypeAttributes

ForeachTypeAttribute: enum ref scope type, TypeCtor

ForeachAggregate: expression, Expression

ForeachAggregate is evaluated. It must evaluate to an expression which is a static array, dynamic array, associative array, struct, class, delegate, or sequence. The <em>NoScopeNonEmptyStatement</em> is executed, once for each element of the aggregate.

The number of variables declared in ForeachTypeList depends on the kind of aggregate. The declared variables are set at the start of each iteration.

  • By default a single declared variable is a copy of the current element.
  • If the ForeachTypeAttribute is ref, that variable will be a reference to the current element of the aggregate.
  • If the ForeachTypeAttribute is scope, the variable will have scope semantics.

If not specified, the type of a ForeachType variable can be inferred from the type of the ForeachAggregate. Note that auto is not a valid ForeachTypeAttribute. The two foreach statements below are equivalent:

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

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

foreach (n; arr) // ok, n is an int
    writeln(n);

The aggregate must be loop invariant, meaning that elements cannot be added or removed from it in the <em>NoScopeNonEmptyStatement</em>.

A BreakStatement in the body of the foreach will exit the loop. A ContinueStatement will immediately start the next iteration.

Foreach over Arrays

If the aggregate is a static or dynamic array, there can be one or two variables declared. If one, then the variable is said to be the value, which is set successively to each element of the array. The type of the variable, if specified, must be compatible with the array element type (except for the special handling of character elements outlined below). The <em>value</em> variable can modify array elements when declared with `ref`.

If there are two variables declared, the first is said to be the index and the second is said to be the value as above. index cannot be declared with ref. It is set to the index of the array element on each iteration. The index type can be inferred:

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

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

For a dynamic array, the index type must be compatible with size_t. Static arrays may use any integral type that spans the length of the array.

For foreach, the elements for the array are iterated over starting at index 0 and continuing to the last element of the array. For foreach_reverse, the array elements are visited in the reverse order.

Foreach over Arrays of Characters

If the aggregate expression is a static or dynamic array of chars, wchars, or dchars, then the type of the value variable can be any of char, wchar, or dchar. In this manner any UTF array can be decoded into any UTF type:

char[] a = "\xE2\x89\xA0".dup;  // \u2260 encoded as 3 UTF-8 bytes

foreach (dchar c; a)
{
    writefln("a[] = %x", c); // prints 'a[] = 2260'
}

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

foreach (char c; b)
{
    writef("%x, ", c);  // prints 'e2, 89, a0, '
}

Aggregates can be string literals, which can be accessed as char, wchar, or dchar arrays:

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

which would print:

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

Foreach over Associative Arrays

If the aggregate expression is an associative array, there can be one or two variables declared. If one, then the variable is said to be the value set to the elements of the array, one by one. If the type of the variable is provided, it must implicitly convert from the array element type.

// value type is int
int[string] userAges = ["john":30, "sue":32];

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

If there are two variables declared, the first is said to be the index and the second is said to be the value. The index must be compatible with the indexing type of the associative array. It cannot be ref, and it is set to be the index of the array element.

// index type is string, value type is double
double[string] aa = ["pi":3.14, "e":2.72];

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

The order in which the elements of the array are iterated over is unspecified for foreach. This is why foreach_reverse for associative arrays is illegal.

Foreach over Structs and Classes with opApply

If the aggregate expression is a struct or class object, the foreach is defined by the special opApply member function, and the foreach_reverse behavior is defined by the special opApplyReverse member function. These functions must each have the signature below:

 OpApplyDeclaration:
    int opApply ( scope int delegate ( OpApplyParameters ) dg ) ;

OpApplyParameters: <em>OpApplyParameter</em> <em>OpApplyParameter</em>, <em>OpApplyParameters</em>

OpApplyParameter: ForeachTypeAttributesopt type, BasicType declaration, Declarator

where each OpApplyParameter of dg must match a ForeachType in a <em>ForeachStatement</em>, otherwise the <em>ForeachStatement</em> will cause an error.

Any <em>ForeachTypeAttribute</em> cannot be enum.

To support a ref iteration variable, the delegate must take a ref parameter:
struct S
{
    int opApply(scope int delegate(ref uint n) dg);
}
void f(S s)
{
    foreach (ref uint i; s)
        i++;
}

Above, opApply is still matched when i is not ref, so by using a ref delegate parameter both forms are supported.

There can be multiple opApply and opApplyReverse functions - one is selected by matching each parameter of dg to each ForeachType declared in the ForeachStatement.

The body of the apply function iterates over the elements it aggregates, passing each one in successive calls to the dg delegate. The delegate return value determines whether to interrupt iteration:

  • If the result is nonzero, apply must cease iterating and return that value.
  • If the result is 0, then iteration should continue. If there are no more elements to iterate, apply must return 0.

For example, consider a class that is a container for two elements:

class Foo
{
    uint[2] 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];

    foreach (uint u; a)
    {
        writeln(u);
    }
}

This would print:

73
82
The scope storage class on the dg parameter means that the delegate does not escape the scope of the opApply function (an example would be assigning dg to a global variable). If it cannot be statically guaranteed that dg does not escape, a closure may be allocated for it on the heap instead of the stack.
Annotate delegate parameters to opApply functions with scope when possible.

Important: If opApply catches any exceptions, ensure that those exceptions did not originate from the delegate passed to opApply. The user would expect exceptions thrown from a foreach body to both terminate the loop, and propagate outside the foreach body.

Template opApply

opApply can also be a templated function, which will infer the types of parameters based on the ForeachStatement. For example:

struct S
{
    import std.traits : ParameterTypeTuple;  // introspection template
    import std.stdio;

    int opApply(Dg)(scope Dg dg)
    if (ParameterTypeTuple!Dg.length == 2) // foreach with 2 parameters
    {
        writeln(2);
        return 0;
    }

    int opApply(Dg)(scope Dg dg)
    if (ParameterTypeTuple!Dg.length == 3) // foreach with 3 parameters
    {
        writeln(3);
        return 0;
    }
}

void main()
{
    foreach (int a, int b; S()) { }  // calls first opApply function
    foreach (int a, int b, float c; S()) { }  // calls second opApply function
}

Foreach over Structs and Classes with Ranges

If the aggregate expression is a struct or class object, but the opApply for foreach, or opApplyReverse for foreach_reverse do not exist, then iteration can be done with range primitives. For foreach, this means the following properties and methods must be defined:

Foreach Range Properties
Property Purpose
.emptyreturns true if no more elements
.frontreturn the leftmost element of the range
Foreach Range Methods
Method Purpose
.popFront()move the left edge of the range right by one

Meaning:

foreach (e; range) { ... }

translates to:

for (auto __r = range; !__r.empty; __r.popFront())
{
    auto e = __r.front;
    ...
}

Similarly, for foreach_reverse, the following properties and methods must be defined:

Foreach_reverse Range Properties
Property Purpose
.emptyreturns true if no more elements
.backreturn the rightmost element of the range
Foreach_reverse Range Methods
Method Purpose
.popBack()move the right edge of the range left by one

Meaning:

foreach_reverse (e; range) { ... }

translates to:

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

Example with a linked list:

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);
    }
}

Multiple Element Values

Multiple loop variables are allowed if the front property returns a type that expands to a value sequence whose length matches the number of variables. Each variable is assigned to the corresponding value in the sequence.

struct Tuple(Types...) // takes a TypeSeq
{
    Types items; // ValueSeq
    alias items this; // decay to a value sequence
}

// Infinite range with a repeating element, which is a tuple
struct TupleRange
{
    enum front = Tuple!(char, bool, int)('a', true, 2);
    enum bool empty = false;

    void popFront() {}
}

void main()
{
    // Tuple destructuring
    foreach (a, b, c; TupleRange())
    {
        assert(a == 'a');
        assert(b == true);
        assert(c == 2);
        break;
    }
    // Tuple variable
    foreach (tup; TupleRange())
    {
        assert(tup[0] == 'a');
        assert(tup == TupleRange.front);
        break;
    }
}

See also: std.typecons.Tuple.

Foreach over Delegates

If ForeachAggregate is a delegate, the type signature of the delegate is of the same as for opApply. This enables many different named looping strategies to coexist in the same class or struct.

The delegate can generate the elements on the fly:

// Custom loop implementation, that iterates over powers of 2 with
// alternating sign. The foreach loop body is passed in dg.
int myLoop(int delegate(int) dg)
{
    for (int z = 1; z &lt; 128; z *= -2)
    {
        auto ret = dg(z);

        // If the loop body contains a break, ret will be non-zero.
        if (ret != 0)
            return ret;
    }
    return 0;
}

// Append each value in the iteration to an array
int[] result;
foreach (x; &amp;myLoop)
{
    result ~= x;
}
assert(result == [1, -2, 4, -8, 16, -32, 64, -128]);

Note: When ForeachAggregate is a delegate, the compiler does not try to implement reverse traversal of the results returned by the delegate when foreach_reverse is used. This may result in code that is confusing to read. Therefore, using foreach_reverse with a delegate is now deprecated, and will be rejected in the future.

Foreach over Sequences

If the aggregate expression is a sequence, the loop body is statically expanded once for each element. This is the same as Static Foreach on a sequence.

There can be one or two iteration symbols declared. If one, then the symbol is an element alias of each element in the sequence in turn.

  • If the sequence is a TypeSeq, the element alias is set to each type in turn.
  • If the sequence is a ValueSeq, the element alias is set to each value in turn. If the type of the element alias is given, it must be compatible with the type of every sequence element. If no type is given, the type of the element alias will match the type of each sequence element, which may change between elements.

If there are two symbols declared, the first is the index variable and the second is the element alias. The index must be of int, uint, long or ulong type, it cannot be ref, and it is set to the index of each sequence element.

Examples

import std.meta : AliasSeq;

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

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

Output:

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));
    }
}

Output:

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

Foreach Ref Parameters

ref can be used to modify the elements of the <em>ForeachAggregate</em>. This works for containers that expose lvalue elements, and lvalue sequences.

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

foreach (ref u; a)
{
    u++;
}
foreach (u; a)
{
    writeln(u);
}

which would print:

8
9

ref cannot be applied to an array index variable.

Foreach Restrictions

The aggregate itself must not be resized, reallocated, free'd, reassigned or destructed while foreach is iterating over the elements.

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

foreach (int v; a)
{
    // resizing is unspecified!
    fun();
    a ~= 4;
    a.length += 10;

    // reallocating is unspecified!
    a.reserve(10);

    // reassigning is unspecified!
    a = null;
    a = [5, 6];
}
a ~= 4;   // OK
a = null; // OK
auto aa = [1: 1, 2: 2];

foreach (v; aa)
{
    aa[3] = 3; // unspecified resize
    aa.rehash; // unspecified reallocation
    aa = [4: 4]; // unspecified reassign
}
aa[3] = 3; // OK
aa = null; // OK

Note: Resizing or reassigning a dynamic or associative array during foreach is still @safe.

Foreach Range Statement

A foreach range statement loops over the specified range.

 RangeForeach:
    Foreach ( ForeachType ; LwrExpression .. UprExpression )

LwrExpression: expression, Expression

UprExpression: expression, Expression

ForeachRangeStatement: RangeForeach ScopeStatement

ForeachType declares a variable with either an explicit type, or a common type inferred from LwrExpression and UprExpression. The ScopeStatement is then executed n times, where n is the result of UprExpression - LwrExpression. If UprExpression is less than or equal to LwrExpression, the ScopeStatement is not executed.

If Foreach is foreach, then the variable is set to LwrExpression, then incremented at the end of each iteration. If Foreach is foreach_reverse, then the variable is set to UprExpression, then decremented before each iteration. LwrExpression and UprExpression are each evaluated exactly once, regardless of how many times the ScopeStatement is executed.

import std.stdio;

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

void main()
{
    foreach (i; 0 .. foo())
    {
        write(i);
    }
}

prints:

foo0123456789

Switch Statement

A switch statement goes to one of a collection of case statements depending on the value of the switch expression.

 SwitchStatement:
    switch ( IfCondition ) ScopeStatement

CaseStatement: case expression, ArgumentList : ScopeStatementListopt

DefaultStatement: default : ScopeStatementListopt

ScopeStatementList: StatementListNoCaseNoDefault

StatementListNoCaseNoDefault: StatementNoCaseNoDefault

StatementNoCaseNoDefault StatementListNoCaseNoDefault

StatementNoCaseNoDefault: EmptyStatement

NonEmptyStatementNoCaseNoDefault

ScopeBlockStatement

The <em>Expression</em> from the IfCondition is evaluated. If the type of the <em>Expression</em> is an enum, it is (recursively) converted to its enum, EnumBaseType. Then, if the type is an integral, the <em>Expression</em> undergoes Integer Promotions. Then, the type of the <em>Expression</em> must be either an integral or a static or dynamic array of char, wchar, or dchar.

If an Identifier = prefix is provided, a variable is declared with that name, initialized to the value and type of the <em>Expression</em>. Its scope extends from when it is initialized to the end of the <em>ScopeStatement</em>.

If TypeCtors and/or a specific type is provided for <em>Identifier</em>, those are used for the variable declaration which is initialized by an implicit conversion from the value of the <em>Expression</em>.

The resulting value is compared against each of the case expressions. If there is a match, the corresponding case statement is transferred to.

The case expressions in ArgumentList are a comma separated list of expressions. Each expression must evaluate to a compile-time value or array, or a runtime initialized const or immutable variable of integral type. Each expression must be implicitly convertible to the type of the switch <em>Expression</em>.

Compile-time case values must all be distinct. Const or immutable runtime variables must all have different names. If two case expressions share a value, the first case statement with that value gets control.

The ScopeStatementList introduces a new scope.

A matching break statement will exit the switch BlockStatement.

A switch statement must have exactly one <em>DefaultStatement</em>. If none of the case expressions match, control is transferred to the default statement.

Rationale: This makes it clear that all possible cases are intentionally handled. See also: `final switch`.

foreach (i; 2 .. 10)
{
    bool prime;

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

Case statements and default statements associated with the switch can be nested within block statements; they do not have to be in the outermost block. For example, this is allowed:

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

Implementation Note: The compiler's code generator may assume that the case statements are sorted by frequency of use, with the most frequent appearing first and the least frequent last. Although this is irrelevant as far as program correctness is concerned, it is of performance interest.

Case Range Statement

 CaseRangeStatement:
    case FirstExp : .. case LastExp : ScopeStatementListopt

FirstExp: AssignExpression

LastExp: AssignExpression

A CaseRangeStatement is a shorthand for listing a series of case statements from FirstExp to LastExp, inclusive.

case 1: .. case 3:

The above is equivalent to:

case 1, 2, 3:

No Implicit Fall-Through

A ScopeStatementList must either be empty, or be ended with a ContinueStatement, BreakStatement, ReturnStatement, GotoStatement, expression, ThrowExpression or assert(0) expression unless this is the last case.

switch (i)
{
    case 1:
        message ~= "one";
        // ERROR: implicit fall-through
    case 2:
        // valid: the body is empty
    default:
        message ~= "unknown";
}

goto case; can be used for explicit fall-through:

string message;

foreach (i; 1..5)
{
    switch (i)
    {
        default:    // valid: ends with 'throw'
            throw new Exception("unknown number");

        case 3:     // valid: ends with 'break' (break out of the 'switch' only)
            message ~= "three";
            break;

        case 4:     // valid: ends with 'continue' (continue the enclosing loop)
            message ~= "four";
            continue; // don't append a comma

        case 1:     // valid: ends with 'goto' (explicit fall-through to next case.)
            message ~= "&gt;";
            goto case;

        case 2:     // valid: this is the last case in the switch statement.
            message ~= "one or two";
    }
    message ~= ", ";
}
writeln(message);

`goto` also supports jumping to a specific case or the default case statement.

String Switch

Strings can be used in switch expressions. For example:

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

For applications like command line switch processing, this can lead to much more straightforward code, being clearer and less error prone. char, wchar and dchar strings are allowed.

Final Switch Statement

 FinalSwitchStatement:
    final switch ( IfCondition ) ScopeStatement

A final switch statement is just like a switch statement, except that:

  • No DefaultStatement is allowed.
  • No CaseRangeStatements are allowed.
  • If the switch <em>Expression</em> is of enum type, all the enum members must appear in the CaseStatements.
  • The case expressions cannot evaluate to a run time initialized value.
If the <em>Expression</em> value does not match any of the CaseRangeStatements, whether that is diagnosed at compile time or at runtime.

Continue Statement

 ContinueStatement:
    continue Identifieropt ;

continue aborts the current iteration of its innermost enclosing loop statement, and starts the next iteration. If the enclosing loop is a for statement, its Increment clause is executed.

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

foreach (w; words)
{
    if (w.length &lt; 4)
        continue; // skip writeln

    writeln(w);
}

Output:

just
longer
words

If continue is followed by Identifier, the Identifier must be the label of an enclosing loop statement, and the next iteration of that loop is executed. It is an error if there is no such statement.

outer:
foreach (item; list)
{
    // try 3 times
    foreach (i; 0 .. 3)
    {
        if (item.buy())
            continue outer; // skip to next item

        log("attempt failed");
    }
}

Any intervening `finally` clauses are executed, and any intervening synchronization objects are released.

Note: If a finally clause executes a throw out of the finally clause, the continue target is never reached.

Break Statement

 BreakStatement:
    break Identifieropt ;

break exits the innermost enclosing loop or `switch` statement, resuming execution at the statement following it.

const n = 55;

// find the smallest factor of n
foreach (i; 2 .. n)
{
    writeln("Trying: ", i);
    if (n % i == 0)
    {
        writeln("smallest factor is ", i);
        break; // stop looking
    }
}
writeln("finished");

Output:

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

If break is followed by Identifier, the Identifier must be the label of an enclosing loop or switch statement, and that statement is exited. It is an error if there is no such statement.

// display messages cyclically until the shop is closed
outer:
while (true)
{
    foreach (msg; messages)
    {
        if (shop.isClosed())
            break outer; // end the while loop

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

Any intervening `finally` clauses are executed, and any intervening synchronization objects are released.

Note: If a finally clause executes a throw out of the finally clause, the break target is never reached.

Return Statement

 ReturnStatement:
    return expression, Expressionopt ;

return exits the current function and supplies its return value.

<em>Expression</em> is required if the function specifies a return type that is not void. The <em>Expression</em> is implicitly converted to the function return type.

An <em>Expression</em> of type void is allowed if the function specifies a void return type. The <em>Expression</em> will be evaluated, but nothing will be returned. This is useful in generic programming.

Before the function actually returns, any objects with scope storage duration are destroyed, any enclosing finally clauses are executed, any scope(exit) statements are executed, any scope(success) statements are executed, and any enclosing synchronization objects are released.

The function will not return if any enclosing finally clause does a return, goto or throw that exits the finally clause.

If there is an out postcondition, that postcondition is executed after the <em>Expression</em> is evaluated and before the function actually returns.

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

Goto Statement

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

goto transfers to the statement labeled with Identifier.

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

The second form, goto default;, transfers to the innermost DefaultStatement of an enclosing SwitchStatement.

The third form, goto case;, transfers to the next CaseStatement of the innermost enclosing SwitchStatement.

The fourth form, goto case <em>Expression</em>;, transfers to the CaseStatement of the innermost enclosing SwitchStatement

with a matching <em>Expression</em>.

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

Any intervening finally clauses are executed, along with releasing any intervening synchronization mutexes.

It is illegal for a GotoStatement to be used to skip initializations.

With Statement

The with statement is a way to simplify repeated references to the same object.

 WithStatement:
    with ( expression, Expression ) ScopeStatement
    with ( template, Symbol ) ScopeStatement
    with ( template, TemplateInstance ) ScopeStatement

where <em>Expression</em> evaluates to one of:

  • a class reference
  • a struct instance
  • an enum instance
  • a pointer to one of the above

Within the with body the referenced object is searched first for identifier symbols.

enum E { A, B }

void test(E e)
{
    with (e)       // affects the switch statement
    switch (e)
    {
        case A:    // no need for E.A
        case B:
        default:
            break;
    }
}

Below, if ident is a member of the type of expression, the WithStatement:

with (expression)
{
    ...
    ident;
}

is semantically equivalent to:

(auto ref tmp)
{
    ...
    tmp.ident;
}(expression);

Note that <em>Expression</em> only gets evaluated once and is not copied. The with statement does not change what this or super refer to.

For Symbol which is a scope or TemplateInstance, the corresponding scope is searched when looking up symbols. For example:

struct Foo
{
    alias Y = int;
}
...
Y y;        // error, Y undefined
with (Foo)
{
    Y y;    // same as Foo.Y y;
}

Use of with object symbols that shadow local symbols with the same identifier are not allowed. This is to reduce the risk of inadvertent breakage of with statements when new members are added to the object declaration.

struct S
{
    float x;
}

void main()
{
    int x;
    S s;
    with (s)
    {
        x++;  // error, shadows the int x declaration
    }
}

In nested WithStatements, the inner-most scope takes precedence. If a symbol cannot be resolved at the inner-most scope, resolution is forwarded incrementally up the scope hierarchy.

1 import std.stdio;
2 
3 struct Foo
4 {
5     void f() { writeln("Foo.f"); }
6 }
7 
8 struct Bar
9 {
10     void f() { writeln("Bar.f"); }
11 }
12 
13 struct Baz
14 {
15     // f() is not implemented
16 }
17 
18 void f()
19 {
20     writeln("f");
21 }
22 
23 void main()
24 {
25     Foo foo;
26     Bar bar;
27     Baz baz;
28 
29     f();               // prints "f"
30 
31     with(foo)
32     {
33         f();           // prints "Foo.f"
34 
35         with(bar)
36         {
37             f();       // prints "Bar.f"
38 
39             with(baz)
40             {
41                 f();   // prints "Bar.f".  `Baz` does not implement `f()` so
42                        // resolution is forwarded to `with(bar)`'s scope
43             }
44         }
45         with(baz)
46         {
47             f();       // prints "Foo.f".  `Baz` does not implement `f()` so
48                        // resolution is forwarded to `with(foo)`'s scope
49         }
50     }
51     with(baz)
52     {
53         f();           // prints "f".  `Baz` does not implement `f()` so
54                        // resolution is forwarded to `main`'s scope. `f()` is
55                        // not implemented in `main`'s scope, so resolution is
56                        // subsequently forward to module scope.
57     }
58 }

Synchronized Statement

The synchronized statement wraps a statement with mutex locking and unlocking to synchronize access among multiple threads.

 SynchronizedStatement:
    synchronized ScopeStatement
    synchronized ( expression, Expression ) ScopeStatement

A synchronized statement without <em>Expression</em> allows only one thread at a time to execute ScopeStatement by locking a mutex. A global mutex is created, one per synchronized statement. Different synchronized statements will have different global mutexes.

If there is an <em>Expression</em>, it must evaluate to either an Object or an instance of an Interfaces, in which case it is cast to the Object instance that implemented that interface. The mutex used is specific to that Object instance, and is shared by all synchronized statements referring to that instance. If the object's mutex is already locked when reaching the synchronized statement, it will block every thread until that mutex is unlocked by other code.

void work();

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

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

If f and g are called by different threads but with the same argument, the work calls cannot execute simultaneously. If the (o) part of the synchronized statements is removed in one or both functions, then both work calls could execute simultaneously, because they would be protected by different mutexes.

The synchronization gets released even if ScopeStatement terminates with an exception, goto, or return.

This implements a standard critical section.

Synchronized statements support recursive locking; that is, a function wrapped in synchronized is allowed to recursively call itself and the behavior will be as expected: The mutex will be locked and unlocked as many times as there is recursion.

See also synchronized classes.

Try Statement

Exception handling is done with the try-catch-finally statement.

 TryStatement:
    try ScopeStatement Catches
    try ScopeStatement Catches FinallyStatement
    try ScopeStatement FinallyStatement

Catches: Catch

Catch Catches

Catch: catch ( CatchParameter ) NoScopeNonEmptyStatement

CatchParameter: type, BasicType Identifieropt

FinallyStatement: finally NoScopeNonEmptyStatement

CatchParameter declares a variable v of type T, where T is Throwable or derived from Throwable. v is initialized by the throw expression if T is of the same type or a base class of the throw expression. The catch clause will be executed if the exception object is of type T or derived from T.

If just type T is given and no variable v, then the catch clause is still executed.

It is an error if any CatchParameter type T1 hides a subsequent Catch with type T2, i.e. it is an error if T1 is the same type as or a base class of T2.

The FinallyStatement is always executed, whether the try ScopeStatement exits with a goto, break, continue, return, exception, or fall-through.

If an exception is raised in the FinallyStatement and is not caught before the original exception is caught, it is chained to the previous exception via the next member of Throwable. Note that, in contrast to most other programming languages, the new exception does not replace the original exception. Instead, later exceptions are regarded as 'collateral damage' caused by the first exception. The original exception must be caught, and this results in the capture of the entire chain.

Thrown objects derived from the Error class are treated differently. They bypass the normal chaining mechanism, such that the chain can only be caught by catching the first Error. In addition to the list of subsequent exceptions, Error also contains a pointer that points to the original exception (the head of the chain) if a bypass occurred, so that the entire exception history is retained.

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;
}

prints:

finally
catch first
done

A FinallyStatement may not exit with a goto, break, continue, or return; nor may it be entered with a goto.

A FinallyStatement may not contain any Catches. This restriction may be relaxed in future versions.

Scope Guard Statement

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

The ScopeGuardStatement executes <em>NonEmptyOrScopeBlockStatement</em> at the close of the current scope, rather than at the point where the ScopeGuardStatement appears. scope(exit) executes <em>NonEmptyOrScopeBlockStatement</em> when the scope exits normally or when it exits due to exception unwinding. scope(failure) executes <em>NonEmptyOrScopeBlockStatement</em> when the scope exits due to exception unwinding. scope(success) executes <em>NonEmptyOrScopeBlockStatement</em> when the scope exits normally.

If there are multiple ScopeGuardStatements in a scope, they will be executed in the reverse lexical order in which they appear. If any scope instances are to be destroyed upon the close of the scope, their destructions will be interleaved with the ScopeGuardStatements in the reverse lexical order in which they appear.

write("1");
{
    write("2");
    scope(exit) write("3");
    scope(exit) write("4");
    write("5");
}
writeln();

writes:

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();

writes:

0412

A scope(exit) or scope(success) statement may not exit with a throw, goto, break, continue, or return; nor may it be entered with a goto. A scope(failure) statement may not exit with a 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;
}

writes:

Inside bar()
Inside foo()

Catching C++ Class Objects

On many platforms, catching C++ class objects is supported. Catching C++ objects and D objects cannot both be done in the same TryStatement. Upon exit from the Catch, any destructors for the C++ object will be run and the storage used for it reclaimed. C++ objects cannot be caught in @safe code.

Asm Statement

Inline assembler is supported with the asm statement:

 AsmStatement:
    asm function, FunctionAttributesopt { AsmInstructionListopt }

AsmInstructionList: iasm, AsmInstruction ; iasm, AsmInstruction ; AsmInstructionList

An asm statement enables the direct use of assembly language instructions. This makes it easy to obtain direct access to special CPU features without resorting to an external assembler. The D compiler will take care of the function calling conventions, stack setup, etc.

The format of the instructions is, of course, highly dependent on the native instruction set of the target CPU, and so is Inline Assembler. But, the format will follow the following conventions:

  • It must use the same tokens as the D language uses.
  • The comment form must match the D language comments.
  • Asm instructions are terminated by a ;, not by an end of line.

These rules exist to ensure that D source code can be tokenized independently of syntactic or semantic analysis.

For example, for the Intel Pentium:

int x = 3;
asm
{
    mov EAX,x; // load x and put it in register EAX
}

Inline assembler can be used to access hardware directly:

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

For some D implementations, such as a translator from D to C, an inline assembler makes no sense, and need not be implemented. The version statement can be used to account for this:

version (D_InlineAsm_X86)
{
    asm
    {
        ...
    }
}
else
{
    /* ... some workaround ... */
}

Semantically consecutive AsmStatements shall not have any other instructions (such as register save or restores) inserted between them by the compiler.

Pragma Statement

See pragma, PragmaStatement.

Mixin Statement

 MixinStatement:
    mixin ( expression, ArgumentList ) ;

Each expression, AssignExpression in the ArgumentList is evaluated at compile time, and the result must be representable as a string. The resulting strings are concatenated to form a string. The text contents of the string must be compilable as a valid StatementList, and is compiled as such.

import std.stdio;

void main()
{
    int i = 0;
    mixin("
        int x = 3;
        for (; i &lt; 3; i++)
            writeln(x + i, i);
        ");    // ok

    enum s = "int y;";
    mixin(s);  // ok
    y = 4;     // ok, mixin declared y

    string t = "y = 3;";
    //mixin(t);  // error, t is not evaluatable at compile time
    //mixin("y =") 4; // error, string must be complete statement

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

expression, Expressions, arrays, Arrays