Rust does not have support for do { ... } while (...).
I've seen several threads on this forum explaining how one can get around this, and I myself learned to structure code in such a way that I avoid the need for do while loops.
However, I haven't been able to find any discussions regarding why doesn't rust support it. I don't think it is because it would be hard to implement, so I'm really interested in knowing what's the rationale behind skipping this feature.
Is there any place where I can find this topic being discussed earlier (perhaps an old RFC or issue)?
I don’t have a citation for the original design handy, but I am fairly confident this is the reason: because loop {} does the same job better. do while is a rarely needed form of loop, and (in my experience) in cases where while or for does not suffice, it is just as common to need to do something both before and after the exit condition, as to do something only before. loop is more flexible and fundamental, so Rust only has loop.
Also no real citation, but Brian Anderson, one of the most prolific contributors to the language and the compiler, especially in the early days when this decision was likely made (aka pre-1.0 and likely even before the RFC process), has a nice blog post about loop in favour of do {} while and even while loops:
the Rust way of doing this kind of syntax wouldn’t want the requirement of parentheses around the conditional, but then, what terminates the expression?
If it’s the ;, then do-while would only be a statement, but in Rust most things (including all the loops) are expressions
or the while would become an operator with its own precedence and all, but that might be weird, no?
the existence of loop in Rust makes the “workaround” solution somewhat cleaner than the need for while (true) in C
loop {
// loop body
// loop body
if !/* condition */ {
break;
}
}
with the condition inside of the loop, all scoping questions are also pretty much solved
…one downside on the other hand is that continue will always unconditionally start another iteration (which may not be what you want)
in Rust, while also has a while let variant, but do-while couldn’t have something like this (because you wouldn’t have defined the variables it introduces in the first iteration)
Pascal had the "Repeat ... Until" syntax. Wirth emphasized structured programming a lot, and loop {}constructs with multiple breakstatements are not well structured. But in practice the `Repeat Until" construct was not used that often. For
example, for reading in some important data, e.g. a user name, a 'Repeat ... Until' with a final test for a non empty name would make sense -- so that occurred often as an example in old text books. But in practice, a loop with break statements was used, e.g. to terminate the input early, to ignore invalid names, and all that -- often while printing some messages.
Heh… I just had this idea for a … kinda weird … alternative workaround (to avoid the need for ! negation, and also the handful of extra lines for the break under rustfmt formatting):
loop {
// loop body
// loop body
/* condition */ || break;
}
which works – but funnily enough clippy (incorrectly) determines this code to be bugged, producing errors such as
error: this loop never actually loops
--> src/main.rs:6:5
|
6 | / loop {
7 | | println!("{n}");
8 | | n -= 1;
9 | | n >= 0 || break;
10 | | }
| |_____^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#never_loop
= note: `#[deny(clippy::never_loop)]` on by default
I guess if all you wan't is to avoid the negation you could also do
loop {
// loop body
if /* condition */ {
continue;
}
break;
}
But I can't think of a benchmark to test this where the compiler wouldn't optimize this away. I also have no idea how the machine code would look like and if it's worth it.
I find that in Rust I usually want the condition in the middle, not at either end, so end up just using a loop with a break in the middle somewhere, and success.
Isn't that coming from our habit of using while the other way? After all, we have the return expression, or even weirder, the break expression. Or the closure expression. I wouldn't dream of using any of that inside another expression with elements competing for precedence. So a do...while or a do...until could do the same. The latter would also remove any confusion about the lack of let while in that situation—I do miss the until from other languages, even sometimes in iterator adapters. Keeping do...while as an expression would allow its use in closures without the need of a surrounding block, for instance, so it's preferable.
Nevertheless, they're all good reasons. A loop + break does the job, even if it's less clear in this situation (and maybe harder for the compiler to optimize?), but it offers more flexibility while keeping the language simple.
All the flavors of loops in Rust already do desugar to simple loop constructs very early in compilation anyway, if I recall correctly, so there should be no such concern.
Maybe, since I expect the IR to be translated roughly in the same way; typically by inserting a label after the loop block where either until or break would jump before descoping and joining the flow of the next instructions. I'm just thinking that the break version is still inside the block, which could complicate the paths and steps like borrow checking (well, it already has an issue with simple while loops and if/else expressions, but still). Unless a specific case of a break as last statement of a block can be detected, and so on.