www.digitalmars.com
Last update Fri Jun 16 13:32:26 2006

Statements

C and C++ programmers will find the D statements very familiar, with a few interesting additions.
Statement:
    LabeledStatement
    BlockStatement
    ExpressionStatement
    DeclarationStatement
    IfStatement
    DebugStatement
    VersionStatement
    WhileStatement
    DoWhileStatement
    ForStatement
    ForeachStatement
    SwitchStatement
    CaseStatement
    DefaultStatement
    ContinueStatement
    BreakStatement
    ReturnStatement
    GotoStatement
    WithStatement
    SynchronizeStatement
    TryStatement
    ScopeStatement
    ThrowStatement
    VolatileStatement
    AsmStatement
    PragmaStatement

Labelled Statements

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

LabelledStatement:
    Identifier ':' Statement

Any statement can be labelled, including empty statements, and so can serve as the target of a goto statement. Labelled 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.

A block statement introduces a new scope for local symbols. 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.

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 and initialize variables.
DeclarationStatement:
	Type IdentifierList ;

IdentifierList:
	Variable
	Variable , IdentifierList

Variable:
	Identifier
	Identifier = Initializer
If no Initializer is there to initialize the variable, it is initialized to the default value for its type.
int a;		// declare a as type int and initialize it to 0
long* c,d;	// declare c and d as type pointer to long, initialize to null
byte* e,*f;	// illegal declaration of f
short s = 3;	// declare s as type short and initialize it to 3

If Statement

If statements provide simple conditional execution of statements.
IfStatement:
	if ( IfCondition ) ThenStatement
	if ( IfCondition ) ThenStatement else ElseStatement

IfCondition:
	Expression
	auto Identifier = Expression
	Declarator = Expression

ThenStatement:
	Statement

ElseStatement:
	Statement
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 ) Statement
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 Statement is executed. After the Statement is executed, the Expression is evaluated again, and if true the Statement 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 While Statement

DoStatement:
    do Statement while ( Expression )
Do while statements implement simple loops. Statement 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; Test; Increment) Statement

Initialize:
	empty
	Expression
	Declaration

Test:
	empty
	Expression

Increment:
	empty
	Expression
Initializer 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 break statement will exit the loop. A continue statement will transfer directly to the Increment.

If Initializer declares a variable, that variable's scope extends through the end of 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 Initializer 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; Expression) Statement

ForeachTypeList:
	ForeachType
	ForeachType , ForeachTypeList

ForeachType:
	inout Type Identifier
	Type Identifier
	inout Identifier
	Identifier

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

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

If the aggregate expression 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 or uint type, it cannot be inout, and it is set to be the index of the array element.

char[] a;
...
foreach (int i, char c; a)
{
    printf("a[%d] = '%c'\n", i, c);
}
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";	// \u2260 encoded as 3 UTF-8 bytes

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

dchar[] b = "\u2260";

foreach (char c; b)
{
    printf("%x, ", c);	// prints 'e2, 89, a0'
}
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 inout, and it is set to be the index of the array element.
double[char[]] a;	// index type is char[], value type is double
...
foreach (char[] s, double d; a)
{
    printf("a['%.*s'] = %g\n", s, d);
}
If the aggregate expression is a static or dynamic array, the elements are iterated over starting at index 0 and continuing to the maximum of the array. If it is an associative array, the order of the elements is undefined. If it is a struct or class object, it is defined by the special opApply member function.

If the aggregate is a struct or a class object, that struct or class must have an opApply function with the type:

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

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

class Foo
{
    uint array[2];

    int opApply(int delegate(inout 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)
    {
	printf("%d\n", u);
    }
}
which would print:
73
82
Aggregates can be string literals, which can be accessed as char, wchar, or dchar arrays:
void test()
{
    foreach (char c; "ab")
    {
	printf("'%c'\n", c);
    }
    foreach (wchar w; "xy")
    {
	wprintf("'%c'\n", w);
    }
}
which would print:
'a'
'b'
'x'
'y'
inout can be used to update the original elements:
void test()
{
    static uint[2] a = [7, 8];

    foreach (inout uint u; a)
    {
	u++;
    }
    foreach (uint u; a)
    {
	printf("%d\n", u);
    }
}
which would print:
8
9
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
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 ) BlockStatement

CaseStatement:
	case ExpressionList : Statement

DefaultStatement:
	default: Statement
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, ExpressionList, are a comma separated list of expressions.

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

If none of the case expressions match, and there is not a default statement, a SwitchError is thrown. The reason for this is to catch the common programming error of adding a new value to an enum, but failing to account for the extra value in switch statements. This behavior is unlike C or C++.

The case expressions must all evaluate to a constant value or array, and be implicitly convertible to the type T of the switch Expression.

Case expressions must all evaluate to distinct values. There may not be two or more default statements.

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;
    }
Like in C and C++, case statements 'fall through' to subsequent case values. A break statement will exit the switch BlockStatement. For example:
switch (i)
{
    case 1:
	x = 3;
    case 2:
	x = 4;
	break;

    case 3,4,5:
	x = 5;
	break;
}
will set x to 4 if i is 1.

Note: Unlike C and C++, 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. Both ascii and wchar 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.

Continue Statement

ContinueStatement:
    continue;
    continue Identifier ;
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, 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;
    break Identifier ;
A break exits the enclosing statement. break exits the innermost enclosing while, for, 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;
    return Expression ;
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.

Expression is allowed even if the function specifies a void return type. The Expression will be evaluated, but nothing will be returned.

Before the function actually returns, any objects with auto 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 labelled 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 ) BlockStatement
	with ( Symbol ) BlockStatement
	with ( TemplateInstance ) BlockStatement
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
{
    typedef int Y;
}
...
Y y;		// error, Y undefined
with (Foo)
{
    Y y;	// same as Foo.Y y;
}

Synchronize Statement

The synchronize statement wraps a statement with critical section to synchronize access among multiple threads.
SynchronizeStatement:
	synchronized Statement
	synchronized ( Expression ) Statement
synchronized allows only one thread at a time to execute Statement.

synchronized (Expression), where Expression evaluates to an Object reference, allows only one thread at a time to use that Object to execute the Statement. If Expression is an instance of an Interface, it is cast to an Object.

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

Example:

synchronized { ... }
This implements a standard critical section.

Try Statement

Exception handling is done with the try-catch-finally statement.
TryStatement:
	try BlockStatement Catches
	try BlockStatement Catches FinallyStatement
	try BlockStatement FinallyStatement

Catches:
	LastCatch
	Catch
	Catch Catches

LastCatch:
	catch BlockStatement

Catch:
	catch ( CatchParameter ) BlockStatement

FinallyStatement:
	finally BlockStatement
CatchParameter declares a variable v of type T, where T is Object or derived from Object. 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 BlockStatement 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 FinallyStatement is executed, the new exception replaces any existing exception:

int main()
{
    try
    {
	try
	{
	    throw new Exception("first");
	}
	finally
	{
	    printf("finally\n");
	    throw new Exception("second");
	}
    }
    catch(Exception e)
    {
	printf("catch %.*s\n", e.msg);
    }
    printf("done\n");
}
prints:
finally
catch second
done
A FinallyStatement may not exit with a throw, 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 an Object reference. The Object reference is thrown as an exception.
throw new Exception("message");

Scope Statement

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

If there are multiple ScopeStatements in a scope, they are executed in the reverse lexical order in which they appear. If any auto instances are to be destructed upon the close of the scope, they also are interleaved with the ScopeStatements in the reverse lexical order in which they appear.

writef("1");
{
    writef("2");
    scope(exit) writef("3");
    scope(exit) writef("4");
    writef("5");
}
writefln();
writes:
12543
{
    scope(exit) writef("1");
    scope(success) writef("2");
    scope(exit) writef("3");
    scope(success) writef("4");
}
writefln();
writes:
4321
class Foo
{
    this() { writef("0"); }
    ~this() { writef("1"); }
}

try
{
    scope(exit) writef("2");
    scope(success) writef("3");
    auto Foo f = new Foo();
    scope(failure) writef("4");
    throw new Exception("msg");
    scope(exit) writef("5");
    scope(success) writef("6");
    scope(failure) writef("7");
}
catch (Exception e)
{
}
writefln();
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.

Volatile Statement

No code motion occurs across volatile statement boundaries.
VolatileStatement:
	volatile Statement
Statement is evaluated. Memory writes occurring before the Statement are performed before any reads within or after the Statement. Memory reads occurring after the Statement occur after any writes before or within Statement are completed.

A volatile statement does not guarantee atomicity. For that, use synchronized statements.

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:

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 ... */
}