D from the ground up

Decisions and loops

Choosing a path with if and switch, walking a range with foreach, and the rule that makes a D condition different from the same condition in C.

Part 5 of 12

Intermediate17 min readBack to Courses

if, else if, else

An if takes a condition in parentheses and a block in braces. The conditions chain with else if, and a final else catches everything left over. Only one branch of a chain ever runs, and the first condition that holds wins, so the order of the tests is part of the logic.

A condition is read as true or false, and a number converts: zero is false and every other value is true, so if (count) compiles and means the same as if (count != 0). Writing the comparison out is still the habit worth keeping, because the line then says what the branch decided and leaves no rule for a reader to remember.

The braces are optional for a single statement, but keeping them costs two lines and prevents the classic mistake of adding a second statement to a branch and having it run unconditionally. The compiler cannot warn about that one: the code is legal, and only the indentation says otherwise.

switch matches one value

switch compares one value against a list of cases. In D the value can be a string as well as a number, so switch (command) with case "go": is ordinary code. Each case ends with break to leave the switch. Without it control falls into the next case, and the compiler reports that fall-through as a deprecation warning: the behaviour came from C, it is almost always a mistake, and modern D asks for goto case; when the fall-through is really intended.

default catches the values no case mentions. A case can also cover a span — case 1: .. case 5: matches 1 through 5 — which saves a chain of comparisons when the branches are ranges of numbers.

foreach walks a range

foreach (i; 0 .. 3) runs the body with i set to 0, 1 and 2. The range excludes its upper bound, which is why the last step printed is 2 and not 3. The same form walks an array, and the two-variable form gives the index as well: foreach (i, value; xs).

while repeats for as long as a condition holds, which is the right tool when the number of passes is not known in advance. Inside either loop, break leaves at once and continue skips to the next pass.

The two bounds are evaluated once, before the first pass, so changing a variable inside the loop does not extend or shorten the walk. That is a guarantee worth relying on when the body edits the same data it is walking: the loop still visits exactly the elements it started with, even if the array grows.

Short-circuit: the left side can protect the right

&& stops as soon as the left side is false, and || stops as soon as it is true. The right side is not evaluated at all in those cases, and that is a guarantee rather than an optimisation to hope for. It is what makes if (n != 0 && total / n > 2) safe: the division never runs when n is zero.

The order of the two sides is therefore part of the meaning. Put the test that guards the dangerous expression first. A condition written the other way round can divide by zero before it ever reaches the guard.

A switch the compiler can finish

A final switch over an enum must name every member and may not carry a default. The compiler knows the complete list, so it can prove that no value falls through. Add a member to the enum later and every final switch that forgot it stops compiling.

An ordinary switch also works on strings, and a case can cover a span with case 1: .. case 5:. Fall-through is still possible, but the compiler reports it unless the case ends with an explicit goto case;.

A ternary, then a while

import std.stdio;

void main() {
    int n = 7;
    string kind = (n % 2 == 0) ? "even" : "odd";
    writeln(n, " is ", kind);

    int i = 0;
    while (i < 3) {
        i++;
    }
    writeln("i=", i);
}

One grade, one command, three steps

Run it, then change score to 95 and again to 60 and watch which branch prints. Add a case "pause" to the switch, and change the range so the loop counts 1 to 4.

Output
Click "Run" to execute your code

A for loop from C can be written in D, but the reader has to unpack three clauses to see the count. foreach over a range says the same thing in one line, and it cannot run past the end of the array it walks.

A guarded test and a final switch

Run it, then change the starting number to 6 and watch the step count change. Add a `case` to the enum and a matching branch, and confirm the program compiles only when both are present.

Output
Click "Run" to execute your code

foreach (i; 0 .. 3) prints 0, 1 and 2, because the upper bound is excluded; a loop meant to include 3 writes 0 .. 4. A case without break falls into the next one. And a switch accepts whole numbers and strings, not a double, so a fractional value needs if or a conversion first.

Try these next

  • Change the starting number to 6 and watch the step count fall, then try 1 and explain why it stops at once.
  • Remove the zero != 0 guard and describe what the program does instead of printing a result.
  • Add a third enum member and a matching case, and confirm the final switch compiles only when both exist.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

How many times does foreach (i; 0 .. 5) run the body?