In writing my own code lately, I continue to get annoyed with needing to write out wonky boilerplate just to get a post-body while test. Even worse, I find myself eschewing the increment part of Zig’s while construct (and presumably giving up on nice compiler optimizations) when using “while (true)” because it moves the test and increment away from one another visually.
So, I decided to check std to see if I’m just the only person suffering:
Post-body while loop checks seems to be 20% of all while loops. That seems like it passes the threshold to start thinking about an actual language construct.
This doesn’t have to be a whole new grammar construct like do/while. It could be as simple as adding using a new keyword like whilepost that uses all the same while machinery, but only changes the position of the conditional test in the generated code. (Yes, I realize that the coder view of the complexity is easy while hiding complexity in the compiler side of the equation).
Not every while (true) loop is a while (true) { do_stuff; if (cond) break; } loop. If the break is done by a return statement or the break condition is in something like a switch, orelse, catch or nested if, a while loop might still be less complex than a do-while like loop. Undoubtedly some loops might benefit, but not 20% of all loops.
This is by no means against you, but repeat ... until is the devils loop. It just inverts all the other common loop idioms.
Normally in a while or for loop you loop while some predicate holds. repeat until loops while it doesn’t hold.
I still hope we get a do while in zig. It isn’t needed often but when it matches the desired effect it’s much clearer than any workaround with break in various places.
IMHO repeat … until is much clearer than do … while, in particular because I also have seen while loops without a body in some cases. If you read untilthen you can be sure that this ends a loop with a body.
I could agree with this in a vacuum, but assuming that we don’t ignore decades of context, history, and convention in the many other languages that use it, do...while is clearer by virtue alone that it is the far more familiar/common construct.
While I personally appreciate the inclusion of while in Zig and use it often, I have no opposition with languages like Go that just use for for every loop, so take my perspective with a grain of salt.
To give a different data point:
In my game there are 551 while loops, 31 of them are while (true) but only 4 of them actually would be convertible to do while. And one of them would be better implemented as a helper function anyways.
The main reason for this is that often the condition depends on some loop-local variable, or a temporary value (e.g. orelse break), or the loop just wants to return a value when the loop ends (either from a small helper function or a block).
I’m sure when you look through std you’ll find that most while (true) loops you found suffer from similar problems.
On reconsideration: I checked my program. 53 while loops and 13 while (true) loops.
Mostly these last ones are very short and a break is (I did not use Delphi for ages) in most cases shorter and easier on the eyes for me now.
I vaguely remember sometimes using 'repeat … until false` with a break hahaha
How often does it happen that you’d a perform a post-loop test that doesn’t depend on variables that logically should only exist within the scope of the loop?
Experienced developers like @chrboesch misinterpreting this is another indicator that the continue expression should be considered for removal.
An if … break inside the loop, at the place where you need it, is two keywords more boilerplate, but on the plus side, the semantic is absolutely clear.
The continue expression is written in a totally different place (before the body) than executed (after the body); this is confusing.
Do you also find defer confusing for the same reason? Continue expressions are very useful, including multistatement ones, and the Zig compiler has many good examples. Also note it’s not executed after the body per se, it’s more useful to think of it as executing on continue (explicit or not)