Statements - D Programming Language

D Programming Language
Improve this page Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using local clone. Page wiki View or edit the community-maintained wiki page associated with this page.

# Statements

C and C++ programmers will find the D statements very familiar, with a few interesting additions.

    Statement:
        ;
        NonEmptyStatement
        ScopeBlockStatement

    NoScopeNonEmptyStatement:
        NonEmptyStatement
        BlockStatement

    NoScopeStatement:
        ;
        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
        ThrowStatement
        AsmStatement
        PragmaStatement
        MixinStatement
         ForeachRangeStatement
        ConditionalStatement
        StaticAssert
        TemplateMixin
        ImportDeclaration

Any ambiguities in the grammar between Statements and Declarations are resolved by the declarations taking precedence. If a Statement is desired instead, wrapping it 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
    }

The idea is to avoid bugs in complex functions caused by scoped declarations inadvertently hiding previous ones. Local names should all be unique within a function.

### 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 : NoScopeStatement

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.

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.

### 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.

### Expression Statement

    ExpressionStatement:
        Expression ;

The expression is evaluated.

Expressions that have no effect, like (x + x), are illegal in expression statements. If such an expression is needed, casting it to void will make it legal.

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

### Declaration Statement

Declaration statements declare variables and types.

    DeclarationStatement:
        Declaration

Some declaration statements:

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

### If Statement

If statements provide simple conditional execution of statements.

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

    IfCondition:
        Expression
        auto Identifier = Expression
        BasicType Declarator = Expression

    ThenStatement:
        ScopeStatement

    ElseStatement:
        ScopeStatement

Expression is evaluated and must have a type that can be converted to a boolean. If it's true the ThenStatement is transferred to, else the ElseStatement is transferred to.

The 'dangling else' parsing problem is solved by associating the else with the nearest if statement.

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

If a Declarator is provided, it is declared and initialized to the value of the Expression. Its scope extends from when it is initialized to the end of the ThenStatement.

    import std.regexp;
    ...
    if (auto m = std.regexp.search("abcdef", "b(c)d"))
    {
      writefln("[%s]", m.pre);      // prints [a]
      writefln("[%s]", m.post);     // prints [ef]
      writefln("[%s]", m.match(0)); // prints [bcd]
      writefln("[%s]", m.match(1)); // prints [c]
      writefln("[%s]", m.match(2)); // prints []
    }
    else
    {
      writefln(m.post);    // error, m undefined
    }
    writefln(m.pre);         // error, m undefined

### While Statement

    WhileStatement:
        while ( Expression ) ScopeStatement

While statements implement simple loops. Expression is evaluated and must have a type that can be converted to a boolean. If it's true the ScopeStatement is executed. After the ScopeStatement is executed, the Expression is evaluated again, and if true the ScopeStatement is executed again. This continues until the Expression evaluates to false.

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

A BreakStatement will exit the loop. A ContinueStatement will transfer directly to evaluating Expression again.

### Do Statement

    DoStatement:
        do ScopeStatement while ( Expression ) ;

Do while statements implement simple loops. ScopeStatement is executed. Then Expression 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 Expression evaluates to false.

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

A BreakStatement will exit the loop. A ContinueStatement will transfer directly to evaluating Expression again.

### For Statement

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

    ForStatement:
        for (Initialize Testopt ; Incrementopt) ScopeStatement

    Initialize:
        ;
        NoScopeNonEmptyStatement

    Test:
        Expression

    Increment:
        Expression

Initialize is executed. Test is evaluated and must have a type that can be converted to a boolean. If it's true the statement is executed. After the statement is executed, the Increment is executed. Then Test is evaluated again, and if true the statement 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 the end of the for statement. For example:

    for (int i = 0; i < 10; i++)
      foo(i);

is equivalent to:

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

Function bodies cannot be empty:

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

Use instead:

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

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

### Foreach Statement

A foreach statement loops over the contents of an aggregate.

    ForeachStatement:
        Foreach (ForeachTypeList ; Aggregate) NoScopeNonEmptyStatement

    Foreach:
        foreach
        foreach_reverse

    ForeachTypeList:
        ForeachType
        ForeachType , ForeachTypeList

    ForeachType:
        refopt BasicType Declarator
        refopt Identifier

    Aggregate:
        Expression

Aggregate is evaluated. It must evaluate to an expression of type static array, dynamic array, associative array, struct, class, delegate, or tuple. The NoScopeNonEmptyStatement is executed, once for each element of the aggregate. At the start of each iteration, the variables declared by the ForeachTypeList are set to be a copy of the elements of the aggregate. If the variable is ref, it is a reference to the contents of that aggregate.

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

#### 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 set to the elements of the array, one by one. The type of the variable must match the type of the array contents, except for the special cases outlined below. 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 of int, uint or size_t type, it cannot be ref, and it is set to be the index of the array element.

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

For foreach, the elements for the array are iterated over starting at index 0 and continuing to the maximum 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 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:

    void test() {
      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. The type of the variable must match the type of the array contents. 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 of the same type as the indexing type of the associative array. It cannot be ref, and it is set to be the index of the array element. The order in which the elements of the array is unspecified for foreach. foreach_reverse for associative arrays is illegal.

    double[string] a; // index type is string, value type is double
    ...
    foreach (string s, double d; a)
    {
      writefln("a['%s'] = %g", s, d);
    }

#### Foreach over Structs and Classes with Ranges

Iteration over struct and class objects can be done with ranges, which means the following properties and methods must be defined:

Foreach Range Properties
Property | Purpose
--- | ---
.empty | returns true if no more elements
.front | return the leftmost element of the range
.back | return the rightmost element of the range

Foreach Range Methods
Method | Purpose
--- | ---
.popFront() | move the left edge of the range right one
.popBack() | move the right edge of the range left one

Meaning:

    foreach (e; range) { ... }

translates to:

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

Similarly:

    foreach_reverse (e; range) { ... }

translates to:

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

If the foreach range properties do not exist, the opApply method will be used instead.

#### Foreach over Structs and Classes with opApply

If it is a struct or class object, the foreach is defined by the special opApply member function. The foreach_reverse behavior is defined by the special opApplyReverse member function. These special functions must be defined by the type in order to use the corresponding foreach statement. The functions have the type:

    int opApply(int delegate(ref Type [, ...]) dg);

    int opApplyReverse(int delegate(ref Type [, ...]) dg);

where Type matches the Type used in the ForeachType declaration of Identifier. Multiple ForeachTypes correspond with multiple Type's in the delegate type passed to opApply or opApplyReverse. There can be multiple opApply and opApplyReverse functions, one is selected by matching the type of dg to the ForeachTypes of the ForeachStatement. The body of the apply function iterates over the elements it aggregates, passing them each to the dg function. If the dg returns 0, then apply goes on to the next element. If the dg returns a nonzero value, apply must cease iterating and return that value. Otherwise, after done iterating across all the elements, apply will return 0.

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

    class Foo {
      uint[2] array;

      int opApply(int delegate(ref uint) dg)
      {
        int result = 0;

        for (int i = 0; i < array.length; i++)
        {
          result = dg(array[i]);
          if (result)
            break;
        }
        return result;
      }
    }

An example using this might be:

    void test() {
      Foo a = new Foo();

      a.array[0] = 73;
      a.array[1] = 82;

      foreach (uint u; a)
      {
        writefln("%d", u);
      }
    }

which would print:

    73
    82

#### Foreach over Delegates

If Aggregate 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.

#### Foreach over Tuples

If the aggregate expression is a tuple, 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 tuple, one by one. If the type of the variable is given, it must match the type of the tuple contents. If it is not given, the type of the variable is set to the type of the tuple element, which may change from iteration to iteration. 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 of int or uint type, it cannot be ref, and it is set to be the index of the tuple element.

If the tuple is a list of types, then the foreach statement is executed once for each type, and the value is aliased to that type.

    import std.stdio;
    import std.typetuple; // for TypeTuple

    void main() {
      alias TypeTuple!(int, long, double) TL;

      foreach (T; TL)
      {
        writeln(typeid(T));
      }
    }

Prints:

    int
    long
    double

#### Foreach Ref Parameters

ref can be used to update the original elements:

    void test() {
      static uint[2] a = [7, 8];

      foreach (ref uint u; a)
      {
        u++;
      }
      foreach (uint u; a)
      {
        writefln("%d", u);
      }
    }

which would print:

    8
    9

ref can not be applied to the index values.

If not specified, the Types in the ForeachType can be inferred from the type of the Aggregate.

#### Foreach Restrictions

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

    int[] a;
    int[] b;
    foreach (int i; a)
    {
      a = null;       // error
      a.length += 10; // error
      a = b;          // error
    }
    a = null;         // ok

### Foreach Range Statement

A foreach range statement loops over the specified range.

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

    LwrExpression:
        Expression

    UprExpression:
        Expression

ForeachType declares a variable with either an explicit type, or a 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 executed zero times. 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

#### Break and Continue out of Foreach

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

### Switch Statement

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

    SwitchStatement:
        switch ( Expression ) ScopeStatement

    CaseStatement:
        case ArgumentList : ScopeStatementList

    CaseRangeStatement:
        case FirstExp : .. case LastExp : ScopeStatementList

    FirstExp:
        AssignExpression

    LastExp:
        AssignExpression

    DefaultStatement:
        default : ScopeStatementList

    ScopeStatementList:
        StatementListNoCaseNoDefault

    StatementListNoCaseNoDefault:
        StatementNoCaseNoDefault
        StatementNoCaseNoDefault StatementListNoCaseNoDefault

    StatementNoCaseNoDefault:
        ;
        NonEmptyStatementNoCaseNoDefault
        ScopeBlockStatement

Expression is evaluated. The result type T must be of integral type or char[], wchar[] or dchar[]. The result is compared against each of the case expressions. If there is a match, the corresponding case statement is transferred to.

The case expressions, ArgumentList, are a comma separated list of expressions.

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

If none of the case expressions match, and there is a default statement, the default statement is transferred to.

A switch statement must have a default statement.

The case expressions must all evaluate to a constant value or array, or a runtime initialized const or immutable variable of integral type. They must be implicitly convertible to the type of the switch Expression.

Case expressions must all evaluate to distinct values. Const or immutable variables must all have different names. If they share a value, the first case statement with that value gets control. There must be exactly one default statement.

The ScopeStatementList introduces a new scope.

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

A ScopeStatementList must either be empty, or be ended with a ContinueStatement, BreakStatement, ReturnStatement, GotoStatement, ThrowStatement or assert(0) expression unless this is the last case. This is to set apart with C's error-prone implicit fall-through behavior. goto case; could be used for explicit fall-through:

    int number;
    string message;
    switch (number)
    {
      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;

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

      case 6:     // ERROR: implicit fall-through
        message ~= "six ";

      case 1:     // valid: the body is empty
      case 2:     // valid: this is the last case in the switch statement.
        message = "one or two";
    }

A break statement will exit the switch BlockStatement.

Strings can be used in switch expressions. For example:

    char[] 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.

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.

### Final Switch Statement

    FinalSwitchStatement:
        final switch ( Expression ) ScopeStatement

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

- No DefaultStatement is allowed.
- No CaseRangeStatements are allowed.
- If the switch Expression is of enum type, all the enum members must appear in the CaseStatements.
- The case expressions cannot evaluate to a run time initialized value.

### Continue Statement

    ContinueStatement:
        continue Identifieropt ;

A continue aborts the current iteration of its enclosing loop statement, and starts the next iteration.

continue executes the next iteration of its innermost enclosing while, for, foreach, or do loop. The increment clause is executed.

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

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

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

    for (i = 0; i < 10; i++)
    {
      if (foo(i))
        continue;
      bar();
    }

### Break Statement

    BreakStatement:
        break Identifieropt ;

A break exits the enclosing statement. break exits the innermost enclosing while, for, foreach, do, or switch statement, resuming execution at the statement following it.

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

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

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

    for (i = 0; i < 10; i++)
    {
      if (foo(i))
        break;
    }

### Return Statement

    ReturnStatement:
        return Expressionopt ;

A return exits the current function and supplies its return value. Expression is required if the function specifies a return type that is not void. The Expression is implicitly converted to the function return type.

At least one return statement, throw statement, or assert(0) expression is required if the function specifies a return type that is not void, unless the function contains inline assembler code.

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 (see Contract Programming), that postcondition is executed after the Expression 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 ;

A 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 Expression;, transfers to the CaseStatement of the innermost enclosing SwitchStatement with a matching Expression.

    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 ) ScopeStatement
        with ( Symbol ) ScopeStatement
        with ( TemplateInstance ) ScopeStatement

where Expression evaluates to a class reference or struct instance. Within the with body the referenced object is searched first for identifier symbols. The WithStatement

    with (expression)
    {
      ...
      ident;
    }

is semantically equivalent to:

    {
      Object tmp;
      tmp = expression;
      ...
      tmp.ident;
    }

Note that Expression only gets evaluated once. 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 int Y;
    }
    ...
    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 inadvertant 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
      }
    }

### Synchronized Statement

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

    SynchronizedStatement:
        synchronized ScopeStatement
        synchronized ( Expression ) ScopeStatement

Synchronized allows only one thread at a time to execute ScopeStatement by using a mutex.

What mutex is used is determined by the Expression. If there is no Expression, then a global mutex is created, one per such synchronized statement. Different synchronized statements will have different global mutexes.

If there is an Expression, it must evaluate to either an Object or an instance of an Interface, 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.

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

Example:

    synchronized { ... }

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.

### Try Statement

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

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

    Catches:
        LastCatch
        Catch
        Catch Catches

    LastCatch:
        catch NoScopeNonEmptyStatement

    Catch:
        catch ( CatchParameter ) NoScopeNonEmptyStatement

    CatchParameter:
        BasicType Identifier

    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.

LastCatch catches all exceptions.

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 Error 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 {
          writefln("finally");
          throw new Exception("second");
        }
      }
      catch(Exception e) {
        writefln("catch %s", e.msg);
      }
      writefln("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.

### Throw Statement

Throw an exception.

    ThrowStatement:
        throw Expression ;

Expression is evaluated and must be a Throwable reference. The Throwable reference is thrown as an exception.

    throw new Exception("message");

### Scope Guard Statement

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

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

If there are multiple ScopeGuardStatements in a scope, they are executed in the reverse lexical order in which they appear. If any scope instances are to be destructed upon the close of the scope, they also are 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

    class Foo {
      this() { write("0"); }
      ~this() { write("1"); }
    }

    try {
      scope(exit) write("2");
      scope(success) write("3");
      scope Foo f = new Foo();
      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.

### Asm Statement

Inline assembler is supported with the asm statement:

    AsmStatement:
        asm { }
        asm { AsmInstructionList }

    AsmInstructionList:
        AsmInstruction ;
        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 implementation defined. 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

    PragmaStatement:
        Pragma NoScopeStatement

### Mixin Statement

    MixinStatement:
        mixin ( AssignExpression ) ;

The AssignExpression must evaluate at compile time to a constant 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 j;
      mixin("
        int x = 3;
        for (int i = 0; i < 3; i++)
          writefln(x + i, ++j);
        ");    // ok

      const char[] s = "int y;";
      mixin(s);  // ok
      y = 4;     // ok, mixin declared y

      char[] 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
    }

Forums | Comments | Search | Downloads | Home
Copyright © 1999-2013 by Digital Mars ®, All Rights Reserved | Page generated by Ddoc on Sun Feb 10 15:56:17 2013