GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||
GeezerVison is showing dlang.org — Statements - D Programming Language Language Reference
Report a bug
If you spot a problem with this page, click here to create a GitHub issue.
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
a local clone.
Statements Contents
The order of execution within a function is controlled by Statements. A function's body consists of a sequence of zero or more Statements. Execution occurs in lexical order, though certain statements may have deferred effects. A Statement has no value; it is executed for its effects. Statement:
EmptyStatement
NonEmptyStatement
ScopeBlockStatement
EmptyStatement:
;
NoScopeNonEmptyStatement:
NonEmptyStatement
BlockStatement
NoScopeStatement:
EmptyStatement
NonEmptyStatement
BlockStatement
NonEmptyOrScopeBlockStatement:
NonEmptyStatement
ScopeBlockStatement
NonEmptyStatement:
NonEmptyStatementNoCaseNoDefault
CaseStatement
CaseRangeStatement
DefaultStatement
NonEmptyStatementNoCaseNoDefault:
LabeledStatement
ExpressionStatement
DeclarationStatement
IfStatement
WhileStatement
DoStatement
ForStatement
ForeachStatement
SwitchStatement
FinalSwitchStatement
ContinueStatement
BreakStatement
ReturnStatement
GotoStatement
WithStatement
SynchronizedStatement
TryStatement
ScopeGuardStatement
AsmStatement
MixinStatement
ForeachRangeStatement
PragmaStatement
ConditionalStatement
StaticForeachStatement
ImportDeclaration
Any ambiguities in the grammar between Statements and Declarations are resolved by the declarations taking precedence. Wrapping such a statement in parentheses will disambiguate it in favor of being a Statement. Scope StatementsScopeStatement:
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 } Best Practices: Local declarations within a function should
all have unique names, even if they are in non-overlapping scopes.
Scope Block StatementsScopeBlockStatement:
BlockStatement
A scope block statement introduces a new scope for the BlockStatement. Labeled StatementsStatements 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 StatementBlockStatement:
{ }
{ 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 StatementExpressionStatement:
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 StatementDeclaration statements define variables, and declare types, templates, functions, imports, conditionals, static foreaches, and static asserts. DeclarationStatement:
StorageClassesopt 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 StatementIf statements provide simple conditional execution of statements. IfStatement:
if ( IfCondition ) ThenStatement
if ( IfCondition ) ThenStatement else ElseStatement
IfCondition:
Expression
IfConditionStorageClasses Identifier = Expression
IfConditionStorageClassesopt BasicType Declarator = Expression
IfConditionStorageClasses:
IfConditionStorageClass
IfConditionStorageClass IfConditionStorageClasses
IfConditionStorageClass:
scope
auto
ref
TypeCtor
ThenStatement:
ScopeStatement
ElseStatement:
ScopeStatement
If there is a declared Identifier variable, it is evaluated. Otherwise, Expression is evaluated. The result is converted to a boolean. If the boolean is true, the ThenStatement is executed, otherwise the ElseStatement is executed. The ElseStatement is associated with the innermost if statement which does not already have an associated ElseStatement. From 2.112, it is an error to use else when there is an unmatched else clause from an earlier IfStatement: bool b; int c; if (b) if (c) return; else // Error: else is dangling, add { } after condition c++; Instead, use a BlockStatement to contain the second IfStatement. Rationale: It can be ambiguous as to which IfStatement a dangling else
relates to. Making bug-prone syntax illegal reduces the potential for bugs.
Boolean ConversionThe following type instances are supported in a condition:
If none of these are valid, it is an error to use the value in a condition. Note: A dynamic array a can be used in a condition, which is true when
a !is [].
However, using an array as a boolean condition should be avoided as it can be bug-prone.
This may be disallowed in a future edition.
Condition VariablesWhen an Identifier form of IfCondition is used, a variable is declared with that name and initialized to the value of the Expression.
The scope of the variable is the ThenStatement 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 StatementWhileStatement:
while ( IfCondition ) ScopeStatement
A While Statement implements a simple loop. If the IfCondition is an Expression, it 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; } If the IfCondition declares a variable, it is initialized and evaluated on every loop iteration. A BreakStatement will exit the loop. A ContinueStatement will transfer directly to evaluating IfCondition again. Do StatementDoStatement:
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 StatementFor 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 Test is true the ScopeStatement is executed. After execution, Increment is executed. Then Test is evaluated again, and if true the ScopeStatement 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 ScopeStatement. For example: for (int i = 0; i < 10; i++) foo(i);is equivalent to: {
int i;
for (i = 0; i < 10; i++)
foo(i);
}
ScopeStatement cannot be an empty statement: for (int i = 0; i < 10; i++) ; // illegalUse instead: for (int i = 0; i < 10; i++) { } Initialize may be just ;. Test may be omitted, and if so, it is treated as if it evaluated to true. Best Practices: 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 StatementA foreach statement iterates a series of values. AggregateForeach:
Foreach ( ForeachTypeList ; ForeachAggregate )
ForeachStatement:
AggregateForeach NoScopeNonEmptyStatement
Foreach:
foreach
foreach_reverse
ForeachTypeList:
ForeachType
ForeachType , ForeachTypeList
ForeachType:
ForeachTypeAttributesopt BasicType Declarator
ForeachTypeAttributesopt Identifier
ForeachTypeAttributesopt alias Identifier
ForeachTypeAttributesopt ForeachTuplePattern
ForeachTypeAttributes:
ForeachTypeAttribute
ForeachTypeAttribute ForeachTypeAttributes
ForeachTypeAttribute:
enum
ref
scope
TypeCtor
ForeachAggregate:
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 NoScopeNonEmptyStatement 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.
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 NoScopeNonEmptyStatement. A BreakStatement in the body of the foreach will exit the loop. A ContinueStatement will immediately start the next iteration. Foreach over ArraysIf 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 value 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, unless the array length is statically known to fit in a smaller integer type. 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 CharactersIf 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 ArraysIf 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, which is set to each value in the array, one by one. If the type of the variable is provided, it must implicitly convert from the associative array value 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 variable type (if given) must be implicitly convertible from the key type of the associative array. The index variable is set to the key corresponding to each value in the associative array. // 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); } Implementation Defined: 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.
If an index or value variable is declared as ref, then any type given must match the associative array key or value type respectively, or const-convert from it. A ref index variable must be (inferred as) const, as keys must not be mutated. Foreach over Structs and Classes with opApplyIf 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:
OpApplyParameter
OpApplyParameter, OpApplyParameters
OpApplyParameter:
ForeachTypeAttributesopt BasicType Declarator
where each OpApplyParameter of dg must match a ForeachType in a ForeachStatement, otherwise the ForeachStatement will cause an error. Any ForeachTypeAttribute 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++; } 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:
Undefined Behavior: Returning a nonzero value that is not the result of calling the delegate.
The result of calling the delegate will be nonzero if the ForeachStatement body executes a matching BreakStatement, ReturnStatement, or GotoStatement whose matching label is outside the ForeachStatement. For example, consider a class that is a container for two elements: class Foo { uint[] array; int opApply(scope int delegate(ref uint) dg) { foreach (e; array) { int result = dg(e); if (result) return result; } return 0; } } void main() { import std.stdio; Foo a = new Foo(); a.array = [73, 82, 2, 9]; foreach (uint u; a) { if (u < 5) break; writeln(u); } } 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.
Best Practices: 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 opApplyopApply 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 RangesIf the ForeachAggregate is a struct or class object, but opApply does not exist, then foreach iteration will use range primitives. These primitives produce a series of elements. The following properties and methods must be defined for the ForeachAggregate:
A temporary variable is used for iteration, which is initialized from the ForeachAggregate. The statement: foreach (e; range) { ... }
translates to: for (auto __r = range; !__r.empty; __r.popFront()) { auto e = __r.front; ... } Similarly, for foreach_reverse when opApplyReverse is not defined, range primitives will be used. The following properties and methods must be defined for the ForeachAggregate:
The statement: foreach_reverse (e; range) { ... }
translates to: for (auto __r = range; !__r.empty; __r.popBack()) { auto e = __r.back; ... } The ForeachType variable may be declared with ref when front (or back for foreach_reverse) is a Ref Function: struct R { int[] a; // range primitives: bool empty() => a.length == 0; ref front() => a[0]; void popFront() { a = a[1..$]; } } R r = R([1, 2, 3]); foreach (ref e; r) e *= 2; assert(r.a == [2, 4, 6]); 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 n = new Node(1, new Node(2, null)); auto r = List(n); foreach (e; r) { writeln(e); } } Multiple Element ValuesMultiple 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. If there is only one variable, it will be a value sequence. import std.stdio; // simple version of std.typecons.Tuple struct Tuple(Types...) // takes a TypeSeq { Types items; // ValueSeq alias items this; // decay to a value sequence } string s = "hello"; struct TupleRange { size_t i; auto front() => Tuple!(size_t, char)(i, s[i]); bool empty() => i >= s.length; void popFront() { i++; } } void main() { // Tuple destructuring foreach (i, c; TupleRange()) { writeln(i, ": ", c); } writeln(); // Tuple variable foreach (tup; TupleRange()) { writefln("(%s, %s)", tup[0], tup[1]); } } See also: std.typecons.Tuple. Foreach over DelegatesIf ForeachAggregate is a delegate, the type signature and behavior of the delegate is 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(scope int delegate(int) dg) { for (int z = 1; z < 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; &myLoop) { result ~= x; } assert(result == [1, -2, 4, -8, 16, -32, 64, -128]); foreach_reverse with a delegate is an error. Foreach over SequencesIf the aggregate expression is a sequence, the loop body is statically expanded once for each element. This is similar to a StaticForeachStatement on a sequence, though there is a separate scope for each expansion. There can be one or two iteration symbols declared. If one, then it's an element symbol representing each element in the sequence in turn.
If there are two symbols declared, the first is the index and the second is the element. The index symbol must be of int, uint, long or ulong type (if specified), it cannot be ref, and it is set to the index of each sequence element. Example: 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() Example: 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[] Symbol KindsEach ForeachType declaration (without alias/enum) will attempt to generate:
alias or enum can be specified in a ForeachType:
void main() { import std.meta : AliasSeq; int i = 1; // without `alias`, `a` would be a separate variable initialized from `i` foreach (alias a; AliasSeq!(i, i)) { a++; } assert(i == 3); foreach (e; AliasSeq!(i, 1)) { static if (__traits(compiles, &e)) assert(e == 3); // first e is a runtime variable else static assert(e == 1); // second e is a compile-time constant } } Foreach Ref Parametersref can be used to modify the elements of the ForeachAggregate. 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); } 8 9 ref cannot be applied to an array index variable. Foreach RestrictionsThe 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.
Unpacking Foreach VariablesSimilarly to unpacking variable declarations, AggregateForeach supports unpacking ForeachType declarations. The auto storage class is implied for a tuple pattern unless overridden. ForeachTuplePattern:
( ForeachTupleComponents )
ForeachTupleComponents:
ForeachTupleComponent ,
ForeachTupleComponent , ForeachTupleComponent
ForeachTupleComponent , ForeachTupleComponents
ForeachTupleComponent:
ForeachTypeAttributesopt Identifier
ForeachTypeAttributesopt BasicType TypeSuffixesopt Identifier
ForeachTypeAttributesopt ForeachTuplePattern
The following foreach statements are equivalent: import std.typecons : tuple; auto arr = [tuple(1, "2"), tuple(3, "4"), tuple(5, "6")]; foreach ((x, y); arr) { writeln(x, " ", y); // "1 2\n3 4\n5 6\n" } foreach ((int x, string y); arr) { writeln(x, " ", y);// "1 2\n3 4\n5 6\n" } An index variable can be declared alongside an unpacked element variable (when the ForeachAggregate supports it): import std.typecons : tuple; auto arr = [tuple(1, 2), tuple(3, 4)]; // declare index for array & unpack tuple elements foreach (i, (x, y); arr) { assert(x == 2 * i + 1); assert(y == 2 * i + 2); } Nested tuples can be unpacked. Below, enumerate returns a range of element type Tuple!(size_t, Tuple!(int, int)). The outer tuple is implicitly unwrapped: import std.range : enumerate; import std.typecons : tuple; auto arr = [tuple(1, 2), tuple(3, 4)]; // unpack a range of nested tuples foreach (i, (j, k); enumerate(arr)) { writeln(i," ",j," ",k); // "0 1 2\n1 3 4\n" } The index variable can also be unpacked: import std.typecons : tuple; auto aa = [tuple(1, 2): "hi", tuple(3, 4): "bye"]; // unpack AA tuple keys foreach ((a, b), s; aa) { writeln(a, b, s); // "12hi\n34bye\n" } Normal ForeachTypeAttribute storage classes are supported, and they can apply to tuple component variables: import std.typecons : tuple; auto arr = [tuple(1, 2), tuple(3, 4)]; foreach ((ref x, y); arr) { x = 2 * y; } assert(arr == [tuple(4, 2), tuple(8, 4)]); foreach (const (x, y); arr) { static assert(is(typeof(x) == const(int))); static assert(is(typeof(y) == const(int))); assert(x == 2 * y); } Unpacking can be used with StaticForeach: import std.typecons : tuple; static foreach ((a, b); [tuple(1, 2), tuple(3, 4)]) { pragma(msg, a, b); // "12\n34\n" } Unpacking can also be used with opApply. Foreach Range StatementA foreach range statement loops over the specified range. RangeForeach:
Foreach ( ForeachType ; LwrExpression .. UprExpression )
LwrExpression:
Expression
UprExpression:
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); } } foo0123456789 Switch StatementA switch statement transfers execution to one of a collection of case statements depending on the value of the switch expression.SwitchStatement:
switch ( IfCondition ) ScopeStatement
CaseStatement:
case ArgumentList : ScopeStatementListopt
DefaultStatement:
default : ScopeStatementListopt
ScopeStatementList:
StatementListNoCaseNoDefault
StatementListNoCaseNoDefault:
StatementNoCaseNoDefault
StatementNoCaseNoDefault StatementListNoCaseNoDefault
StatementNoCaseNoDefault:
EmptyStatement
NonEmptyStatementNoCaseNoDefault
ScopeBlockStatement
The Expression from the IfCondition is evaluated.
If an Identifier = prefix is provided, a variable is declared with that
name, initialized to the value and type of the Expression. Its scope
extends from when it is initialized to the end of the ScopeStatement.
If TypeCtors and/or a specific type is provided for Identifier, those
are used for the variable declaration which is initialized by an implicit
conversion from the value of the Expression.
The resulting value is compared against each of the case expressions. If there is a match, the corresponding case statement is transferred to. A switch statement must have exactly one DefaultStatement. 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.
Case and Default StatementsA CaseStatement's ArgumentList is 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 Expression. Compile-time case values must all be distinct. Const or immutable runtime case 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's ScopeStatement. 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 StatementCaseRangeStatement:
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-ThroughA ScopeStatementList must either be empty, the last one in the switch, or be ended by:
Simple forms of the last 2 cases above can be recognized by the compiler, but not all. uint i; string message; switch (i) { case 1: message = "one"; // ERROR: implicit fall-through case 2: // valid: the body is empty default: message = "2 or more"; // valid: no more case statements } goto case; can be used for explicit fall-through to the next CaseStatement: string message; foreach (i; 1..5) { switch (i) { default: // valid: ends with `noreturn` expression evaluation 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 ~= ">"; 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 to the default case statement. String SwitchStrings can be used in a switch expression. Static or dynamic arrays of char, wchar or dchar are supported. For example: string name = "fred"; switch (name) { case "fred", "sally": writeln("Hi ", name); break; default: writeln("Not recognized"); } Rationale: For applications like command-line switch processing, this
can lead to much more straightforward code, being clearer and
less error prone.
Final Switch StatementFinalSwitchStatement:
final SwitchStatement
A final switch statement is just like a switch statement, except that:
enum E { unknown, yes, no } E e; with (E) final switch (e) { case unknown: writeln("need answer"); break; case yes, no: writeln("got answer"); } Implementation Defined: If the Expression value does not match any
of the CaseRangeStatements, whether that is diagnosed at compile
time or at runtime.
Continue StatementContinueStatement:
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 < 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 StatementBreakStatement:
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 StatementReturnStatement:
return Expressionopt ;
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. An Expression of type void is allowed if the function specifies a void return type. The Expression 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 Expression is evaluated and before the function actually returns. int foo(int x) { return x + 3; } Goto StatementGotoStatement:
goto Identifier ;
goto default ;
goto case ;
goto case Expression ;
goto Identifier; transfers execution to a LabeledStatement if a label in the current function matches Identifier. int x = 1; if (x) goto L1; x = 3; L1: assert(x == 1); 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. Expression must be evaluable at compile-time. int x = 5; switch (x) { case 3: writeln("case 3"); goto case; case 4: writeln("case 4"); goto default; case 5: writeln("case 5"); goto case 4; default: writeln("default"); 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 within its containing scope. Best Practices: Prefer using a higher-level control-flow construct
or a labelled BreakStatement/ContinueStatement
rather than the goto Identifier; form.
With StatementThe with statement is a way to simplify repeated references to the same object. WithStatement:
with ( IfCondition ) ScopeStatement
with ( Symbol ) ScopeStatement
For the IfCondition form, Expression must be a user-defined type instance, or a pointer to a user-defined type instance. Within the ScopeStatement the referenced expression/symbol 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 expr, the WithStatement: with (expr)
{
...
ident;
}
is semantically equivalent to:
{
auto ref tmp = expr;
...
tmp.ident;
}
Note that Expression only gets evaluated once and is not copied. The with statement does not change what this or super refer to. For a Symbol argument, 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. import std.stdio; struct Foo { void f() { writeln("Foo.f"); } } struct Bar { void f() { writeln("Bar.f"); } } struct Baz { // f() is not implemented } void f() { writeln("f"); } void main() { Foo foo; Bar bar; Baz baz; f(); // prints "f" with(foo) { f(); // prints "Foo.f" with(bar) { f(); // prints "Bar.f" with(baz) { f(); // prints "Bar.f". `Baz` does not implement `f()` so // resolution is forwarded to `with(bar)`'s scope } } with(baz) { f(); // prints "Foo.f". `Baz` does not implement `f()` so // resolution is forwarded to `with(foo)`'s scope } } with(baz) { f(); // prints "f". `Baz` does not implement `f()` so // resolution is forwarded to `main`'s scope. `f()` is // not implemented in `main`'s scope, so resolution is // subsequently forward to module scope. } } Synchronized StatementThe synchronized statement wraps a statement with mutex locking and unlocking to synchronize access among multiple threads. SynchronizedStatement:
synchronized ScopeStatement
synchronized ( Expression ) ScopeStatement
A synchronized statement without Expression 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 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. 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 methods. Try StatementException 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:
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; } 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 StatementScopeGuardStatement:
scope ( exit ) NonEmptyOrScopeBlockStatement
scope ( success ) NonEmptyOrScopeBlockStatement
scope ( failure ) NonEmptyOrScopeBlockStatement
If NonEmptyOrScopeBlockStatement is executed, it will occur at the close of the current scope, rather than at the point where the ScopeGuardStatement appears:
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(); 12543 {
scope(exit) write("1");
scope(success) write("2");
scope(exit) write("3");
scope(success) write("4");
}
writeln();
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(); 0412A 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; } Inside bar() Inside foo() Catching C++ Class ObjectsOn 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 StatementInline assembler is supported with the asm statement: AsmStatement:
asm FunctionAttributesopt { AsmInstructionListopt }
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:
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 StatementSee PragmaStatement .Mixin StatementMixinStatement:
mixin ( ArgumentList ) ;
Each 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 < 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 } Copyright © 1999-2026 by the D Language Foundation | Page generated by
Ddoc on Mon Nov 9 05:59:18 2026
| ||||||||||||||||||||
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |