18
$\begingroup$

One somewhat rarely used, but still occasionally handy, feature of many programming languages is do ... while loops:

var position;

do {
    position = random_coords();
} while (is_inside_wall(position));

A do ... while is similar to a normal while loop, except that the body runs once before the condition is ever checked.

I've noticed Rust, a language that generally has well-thought-through syntax, omits this. Thus, it seems likely there are some disadvantages of do ... while loops.

What are the pros and cons of do ... while?

$\endgroup$
8
  • $\begingroup$ The question title doesn't match the question in the body. "What are the pros and cons..." vs "what are some ... disadvantages of...". $\endgroup$ Commented May 16, 2023 at 19:06
  • $\begingroup$ @AriBrodsky Ah my bad. I'm asking about both pros and cons, will edit once my school's fire drill's over $\endgroup$
    – rydwolf
    Commented May 16, 2023 at 19:06
  • $\begingroup$ Some APLs have repeat…until besides for while…endwhile and allow mix-and-match, inluding repeat…endrepeat and while…until $\endgroup$
    – Adám
    Commented May 16, 2023 at 19:23
  • 4
    $\begingroup$ Scala 3 got rid of do while loops, suggesting people use while { ...body; cond } {} instead of the old while (cond) {...body}. Looks a lot uglier than do while loops, though $\endgroup$
    – user
    Commented May 17, 2023 at 20:22
  • $\begingroup$ @user ...yeah. That just feels like trying to force people to use a fancy new feature or sth $\endgroup$
    – rydwolf
    Commented May 17, 2023 at 20:23

7 Answers 7

18
$\begingroup$

do ... while loops are more natural at the intermediate representation level. A while loop, naively, has to be implemented as two jumps.

while (X) {
  Y
  Z
}

This must be implemented at a low-level as something like this.

loop:
  X
  jump_if_false end_loop
  Y
  Z
  jump loop
end_loop:

A do ... while loop, on the other hand, is always exactly one conditional jump, even with no optimizations applied.

For example,

do {
  X
  Y
} while (Z);

can be written as:

loop:
  X
  Y
  Z
  jump_if_true loop

Now a good optimizer can help you with this, but in a low-level language it might make a big difference.

$\endgroup$
1
  • 5
    $\begingroup$ Just as a side note, it's also common to implement while (X) { Y; } as goto loop_start; do { Y; loop_start: } while (X); for a few reasons. You can align the top of the loop to a cache line boundary, for example. $\endgroup$
    – Pseudonym
    Commented May 17, 2023 at 3:01
15
$\begingroup$

Pro: better fit between syntax and logic

For some algorithms, do ... while loops are the most natural way to express the program logic and prevent code duplication. Consider the example mentioned in the question:

do {
    position = random_coords();
} while (is_inside_wall(position));

That is, we want to pick a random position and then repick as long as the position is inside a wall.

If do ... while loops are not available, this code will probably be written as a while loop instead. The best way to translate the logic is not obvious. One could pull the initial pick out ahead of the loop:

position = random_coords();
while (is_inside_wall(position)) {
    position = random_coords();
}

This code is pretty readable but violates the don't repeat yourself maxim--not too badly in this case, but it gets worse if the body of the loop is more than one statement.

Alternately, we could use a sentinel value to "disable" the check on the first iteration. This could be a separate variable:

var position_set = false;
while (!position_set || is_inside_wall(position)) {
    position = random_coords();
    position_set = true;
}

Or, in some cases, a known invalid value of the variable we're checking:

position = null;
while (position == null || is_inside_wall(position)) {
    position = random_coords();
}

These approaches add complexity and obscure the real loop condition.

Finally, maybe the worst option (though one that I admit I have used before): write an infinite loop and break out of it once the condition is satisfied:

while (true) {
    position = random_coords();
    if (!is_inside_wall(position)) {
        break;
    }
}

This obscures the algorithm even more severely and is highly prone to accidental infinite loops.

In any case, none of these while-loop options read as cleanly as the do ... while version.

$\endgroup$
3
  • 5
    $\begingroup$ Tbf the last one is more sensible than you give it credit for; I believe it's even the recommended approach for Rust. While it's possible to forget something and make an inf loop, that's true of regular while/do-whiles too $\endgroup$
    – rydwolf
    Commented May 16, 2023 at 21:02
  • $\begingroup$ Huh, interesting--and you're right about infinite loops. I changed it to "maybe" the worst. $\endgroup$
    – DLosc
    Commented May 16, 2023 at 21:09
  • 2
    $\begingroup$ while (is_inside_wall(position = random_coords())); $\endgroup$
    – RubenVerg
    Commented May 17, 2023 at 4:55
13
$\begingroup$

Odd syntax

This is primarily a design issue, but do...while loops have odd syntax which might be an issue in languages like Python which use indentation. Not necessarily a deal-breaker, but definitely something to consider if your language uses indentation.

$\endgroup$
7
  • 7
    $\begingroup$ @RadvylfPrograms Putting the condition in front and evaluating it at the end seems like a huge recipe for footguns. $\endgroup$ Commented May 16, 2023 at 19:06
  • 2
    $\begingroup$ @SilvioMayolo Not necessarily, you just need to change how you think about it from "evaluate the condition at the end" to "a normal while loop where you run the block an extra time at the start" $\endgroup$
    – rydwolf
    Commented May 16, 2023 at 19:16
  • 4
    $\begingroup$ There's an interesting ergonomic benefit to the do while (condition) { code } syntax. If the programmer decided they need to switch from while to do-while, it's a 3 character change. Re-arranging the do-while in other languages is significantly more effort. $\endgroup$ Commented May 19, 2023 at 6:04
  • 3
    $\begingroup$ @SilvioMayolo C-style for loops already evaluate the third part of the header at the end and it doesn't seem to confuse anyone. $\endgroup$
    – xigoi
    Commented Jul 8, 2023 at 19:47
  • 2
    $\begingroup$ @xigoi On the contrary I'd argue C-style for loops should also go the way of the dinosaur. Python did it right: for-each loops are the norm, and you have to use while for anything complex. $\endgroup$ Commented Jul 8, 2023 at 20:08
3
$\begingroup$

The main difference between while and do ... while is when the loop condition is checked. For while, the condition is checked before the first iteration, while for do ... while it is checked after at least one iteration has been executed. Hence a developer selects the type of loop depending from the task. Both cases exist and are not artificial, for instance

  • if the list is too long, we truncate it by removing elements. Maybe the list is not too long? Using do ... while would require an extra if for checking if we should start removing the elements starting from, resulting more bulky and complex code.
  • we read records from the file. We may get either the next record or end of file marker, but we need to attempt reading before we get either of these two. Here do ... while is much more natural, even if we could simulate it with while(true) { ... if (eof) break } that needs more attention from the reader.

Hence I think no big harm to have both types of the loop in the language.

$\endgroup$
2
$\begingroup$

This construct, which expresses the logic that the block should be executed once unconditionally and then further iterated on a certain condition, typically has a very unhappy syntax in any language that supplies it.

Placing the test at the end of the block defies the general principle that control logic goes at the header of the block.

But placing the logic in the header tends to suggest the condition is evaluated on entry, and there doesn't seem to be a good word that expresses a DoOnceThenWhile block.

And if the test is brought to the header but only tried after the first iteration, then the rules around scoping of any variable used in the test is also not necessarily obvious - can it refer to variables declared below and inside the block, or must it pre-emptively declare?

Finally in terms of problems, there are still algorithms where the test must occur somewhere in the middle of the block (assuming that entry occurs strictly at the top), so there's still a residuum not fully catered for.

Having put some thought into this in the past, my preference would be for special syntax along the following lines:

While Unbroken
    position = random_coords()
    ....
    Break When not(is_inside_wall(position))
    ....
End While

The key points here being that the While Unbroken explicitly identifies this as a looping construct where at least some part of the block is executed unconditionally, but with special logic inside that determines the exact point of the test and break (without contrivances like While True or While 1=1), and Break When is supplied as a conditional break statement (with the main word 'break' appearing leftmost).

This then caters for both middle-breaking and end-breaking cases.

Most if not all languages already have the basic facilities (such as the ability to express indefinite loops and break out conditionally), if not the specific keywords.

$\endgroup$
2
$\begingroup$

There's one "con" I haven't seen mentioned. One can make an argument that, for most condition-based loops, the condition in question is a pre-condition that must be met for evaluation of the body to be a valid operation. When a programmer uses a do .. while loop, it's rarely the case that the precondition isn't met at all. Instead, EITHER the rest of the code guarantees the precondition is met and so a while loop could have been used but the programmer is just optimizing away the first, redundant test OR the precondition takes a slightly different form, possibly requiring a bit of setup prior to starting the while loop which wouldn't strictly be necessary if we skipped the first test of the condition, so again the programmer is avoiding a little inconvience by skipping the check.

This suggests that a while construct might be the "safer" option -- easier to reason about and less fragile in the face of changes to the surrounding code. So, a language designer might take a "do ... while considered harmful" approach and leave it out of their language for that reason.

Bjarne Stroustrup makes a comment along these lines in "The C++ Programming Language" (see the quote in this Stack Overflow question). I thought I'd read something similar from Guido van Rossum regarding Python's lack of a do .. while statement, too, but all I can find is his general call to reject a do .. while PEP on more general grounds of inelegance and potential confusion (see this post).

$\endgroup$
4
  • 2
    $\begingroup$ I think the reverse of this is actually true; for the same reason a do .. while is bad and inelegant for checking preconditions, a normal while is bad for postconditions, which is the primary use case of do .. while. E.g., generating a random ID that doesn't conflict with an existing one, or a random set of coordinates that isn't inside of a solid object. $\endgroup$
    – rydwolf
    Commented Jul 11, 2023 at 22:26
  • $\begingroup$ Actually, while loops are quite good at testing postconditions. The postcondition for the loop exit is the logical negation of the precondition for the loop body, and the body won't be executed if the postcondition is already met. The potential problem with a do .. while loop is that it may execute the loop body when the postcondition is already satisfied and -- if it does -- then what's the meaning of the "postcondition"? If it's valid for the loop body to be executed when the postcondition is already met, maybe the safest programs are those where every loop is infinite. ;) $\endgroup$
    – K. A. Buhr
    Commented Jul 11, 2023 at 23:10
  • 2
    $\begingroup$ But in, e.g., either of the cases I mentioned, you either know for a fact that whatever initial value you start with is invalid, or wouldn't want to assume a default value even if it does meet the postcondition (e.g. if you initialize the random ID to 0 before a while instead of a do .. while, you can predict the first random ID will always be 0, which could be a security issue). I see what you're saying regarding safety/semantic meaning, I just don't think it's a big issue since typically you only use do .. while in places where it really does make more sense than while. $\endgroup$
    – rydwolf
    Commented Jul 11, 2023 at 23:17
  • $\begingroup$ Iterate-to-fixpoint is the classic example where do/while makes more sense than while. $\endgroup$
    – Pseudonym
    Commented Jul 12, 2023 at 6:03
2
$\begingroup$

Pro: Loop-invariant code hoisting

Suppose there's some loop-invariant code in your loop. Your compiler would just love to move that code out of the loop so it's evaluated only once.

With a do-while loop, you know that the body of the loop will be executed at least once, so the invariant code will always be needed.

do {
    loop_invariant_code();
    other_code();
} while (cond);

/* This is the same! */
loop_invariant_code();
do {
    other_code();
} while (cond);

In a while loop, the condition could be false on the first iteration, so the body may not be executed. In this case, moving the loop invariant code may not be safe (e.g. it might fail in some way, such as a division by zero), or it may significantly slow the "no iterations" case.

while (cond) {
    loop_invariant_code();
    other_code();
}

/* You'll have to optimise it to something more like this. */
if (cond) {
    loop_invariant_code();
    do {
        other_code();
    } while (cond);
}
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .