Rfcs: Support "do while" loops

Created on 8 Oct 2015  Â·  41Comments  Â·  Source: rust-lang/rfcs

Sometimes you want to check the condition at the end and not the beginning.

do {
   do_work();  
} while condition

These substitutes are less than ideal:

do_work();
while condition {
   do_work();
}

loop {
   do_work();
   if !condition { break }
}

A do while loop would be nice in these cases.

T-lang

Most helpful comment

It's not documented because it's more of a weird hack than it is something you should ever really write.

All 41 comments

Not that do-while wouldn’t be nice, but you can already do this:

while {
    do_work();
    condition
} { }

Huh. Didn't know that. That isn't documented. Are the extra {} on the end accidental? I would have thought something would go in them. That is weird.

Runnable example

It's because while evaluates an expression, and an expression can be in
braces. The empty body is because the work was done evaluating the
condition.

On Thu, Oct 8, 2015, 12:58 PM mdinger [email protected] wrote:

Huh. Didn't know that. That isn't documented
http://doc.rust-lang.org/nightly/book/loops.html#while. Are the extra {}
on the end accidental? I would have thought something would go in them.
That is weird.

—
Reply to this email directly or view it on GitHub
https://github.com/rust-lang/rfcs/issues/1313#issuecomment-146669603.

It's not documented because it's more of a weird hack than it is something you should ever really write.

Well, if that is to be the canonical form of it, it should probably be smarter and not require the extra brace set unless someone wants an extra expression.

while {
    do_work();
    condition
}

The extra brace is kinda inconsistent between expressions too. loop and for run it after the loop is complete. while, at the end of every loop.

EDIT: It's not clear how similar the extra {} is supposed to be to for {} else {} either.

It's not a canonical form. It's cleverness permitted by the language
grammar. Imagine the do_work fn returned the condition, you could just
write 'while do_work() {}'

On Thu, Oct 8, 2015, 1:27 PM mdinger [email protected] wrote:

Well, if that is to be the canonical form of it, it should probably be
smarter and not require the extra brace set unless someone wants an extra
expression.

while {
do_work();
condition
}

The extra brace is kinda inconsistent between expressions too. loop and
for run it after the loop is complete. while, at the end of every loop.

—
Reply to this email directly or view it on GitHub
https://github.com/rust-lang/rfcs/issues/1313#issuecomment-146676195.

If you want it to be more obvious, I usually do:

loop {
    do_work();
    if (!condition) { break }
}

as an alternative to do { do_work(); } while (condition);

Me too but it just seems silly not to provide something this simple which is slightly nicer. I mean, while itself if hardly more than the following so there's no dire need for while, but it's nice to have.

loop {
    if !condition { break }
    do_work();
}

I personally don't think that's enough of a reason to add new syntax. do...while is pretty rare in my experience, which IMO means it probably doesn't justify its own syntax. I agree there is little marginal cost to adding it, but they add up over time...

Could add something like:

loop while {
    action();
    more_code();
    condition
}

It could also support stuff like:
loop while some_condition();

Currently, I don't think that do is a reserved keyword.

Or:

loop {
    action();
    more_code();
} while condition

Just so there's a canonical, proper, and non-hacking way to check the condition at the bottom of the {} without break. It doesn't need to be do while.

Ugh, based on my C++ experience do/while loops are 1) rarely needed, 2) ugly as hell, so I'd prefer to put additional sugar somewhere else.
(For example, a nice little extension to loops I _would_ like to see is "loop n times": loop n { ... }. Now loop is almost redundant and it would give it some additional value. The last time I've seen "loop n times" in some educational language in elementary school, but it turns out to be pretty common in numeric or test code!)

How is it ugly aside from a "looks different from a regular while" sense? It's the natural thing to do when you want to exit on a specific condition. It avoids break and it uses the same number of lines of code as a while loop.

do {
   do_work();  
} while condition

Is not ideal either, since the scoping rules don't allow condition to use local bindings from the body of the loop. In that way, the current loop-if is superior.

@petrochenkov: What's wrong with

for _ in 0..n {
    do_work();
}

?

@Ticki
Nothing, loop n { ... } just looks a bit nicer and less prone to +/-1 mistakes in the range.

:-1:

More complexity to the language grammar with not much to gain.

You can write a _reasonably_ clear macro that packages this up for you:

macro_rules! do_loop {
    (
        $body:block
        while $cond:expr
    ) => {
        while { $body; $cond } { }
    };
}

fn main() {
    let mut i = 0;
    do_loop! {
        {
            println!("i = {}", i);
            i += 1;
        }
        while i < 10
    }
}

Wouldn't it work with while let ?

@snuk182 while let serves a different purpose.

Exceptionally strange to have loop - which could have been fully replaced by while <constant-expression> (or at least a temporary hack for true at the time [to handle the initialization specialcase]) - but make an argument to not offer do..while because of "language complexity".

@sinesc

  1. loop is stable, and you might argue that it should be removed, but ultimately it is a poor reason for adding _more_ complexity.
  2. loop strictly speaking emits different instructions than while true. The former has a unconditional branch in the end, whereas the latter has a conditional one in the end. In practice, though, they're compiled down to the same by LLVM.
  3. loop is not a sugar for a special case of while, it's the other way around. while is actually not as expressive as loop is.
  4. do..while _is_ adding complexity, given that you can already do this with fewer (and more readable) characters.

If do..while was more common, I'd be in favor of adding it, but it isn't.

Not to go further off-topic but just to clarify: from a user's perspective it seems a keyword was essentially "wasted" on an unconditional loop that could be generated from a while <compile-time-constant> when at the same time, rust offers few loop types to begin with.
In other words, I would have wished for a more useful loop in place of loop, e.g. do..while.

While do..while is not often used, I don't see 4. being true unless you wrap the loop contents in a function or stick everything in the condition.

underterminant version looks much nicer than dowhile and have the same functionality. i think dowhile is not necessary. actually after writing 2 years of c# i think rusts loop is much better cause it gives you the controll to break anywhere, not only in the beginning and end, also has nicer syntax

FWIW, you could probably do this as a macro pretty easily. The implementation is left as an exercise to the reader, but something like:

Do! {
    body
    while condition;
 }

Or, if you want to have a stronger separation:

Do! {{
    body
} while condition }

While there's no way (that I can think of) to have the while outside of the body, without an extra set of braces or parenthesis, You can set up the macro– which does pretty powerful syntax matching– to consume and throw away that while token, anywhere in the syntax tree, and then convert the block to an ordinary loop+break.

I think there's a simpler answer, try this

let mut loop = true;
while loop {
    do_work();
    if (condition){loop=false;}
}

It functions like the break but is gentler. Now whether the optimizer will convert them into the same machine code or if not whether this or loop {break} is better I don't know. Might be interesting if someone who understands Rust better could explore an explain (^-^)

I mean, yes, that works, but I think a lot of the whole point of useful control flow structures is that they don't require extraneous variables in order to work.

If you're going to do that, don't even use a variable; just do

loop { do_work(); if condition { break; } }

If you read the op again they explicitly say that's not ideal for them, though why I'm not sure.

I agree that because loop is specifically designed to expect a break that using a loop{break;} probably makes just as much sense to the compiler as a do{} while{} would be and might even compile to the same as the example I gave (making the difference purely about human readability and code consistency) but as I said it might be worth comparing the two

There's no need to put the loop condition check after the body.

Adding do while would force the body to execute once before the condition is checked, do do while would call it twice before checking, etc. (Maybe you only want to support a single do.)

// this does not work, don't try this in rust

do while condition {
   do_work();
}

One reason to put it after the body is that the condition of a do-while loop can (in most languages) depend on variables in the loop:

do {
    let x = get_value();
    let result = do_work_on(x);
} while result > 10;

Syntatically, you could put the while condition at the beginning of the loop, but it would be confusing as a reader to see it depend on variables that are defined after the expression (even though the expression isn't evaluated until much later).

In your example, result goes out of scope before the conditional check?

If you're strictly using { } scoping, yes. However, one of the most common reasons to use a do-while loop is to conditionally continue based on something inside the loop, which is why most languages allow the condition to be based on variables in the loop. C has this behavior, for example.

C has this behavior, for example.

No, it doesn't – unfortunately. Though, if you're using the traditional C style of putting all variable declarations at the top of the function, you can do the equivalent just because everything's in scope everywhere.

The if !condition { break } is also one extra line taking up vertical space.

The macro solution from https://github.com/rust-lang/rfcs/issues/1313#issuecomment-160877552 isn't better in that regard.

And rustfmt makes it even worse:

loop {
   do_work();
   if !condition {
       break;
   }
}

(Couldn't find an existing issue about that though and given https://github.com/rust-lang/rustfmt/issues/2636#issuecomment-477087210 I don't have much hope it would be fixed anytime soon.)

A do while is especially handy for checking input and the like. And it will be more readable than the current

loop {
   do_work();
   if !condition {
      break;
   }
}

I really like this syntax proposed by @mdinger:

loop {
    do_work();
} while condition

The argument that it's unpopular to me is a pretty bad argument. I always miss do while in every language I learn. In rust because of very strict scope, it's even more useful. It's not a crucial feature, but it's also not worth ignoring for such a simple option that some developers in the community obviously love. Don't like it, don't use it, but why should the rest of us not have it?

@ryanpeach

Don't like it, don't use it, but why should the rest of us not have it?

Because it makes the language more complex just by having it, and we don't want to just increase the complexity for a niche feature.

If anyone wants to propose do while loops, they would need to write an actual RFC with everything that involves. Do note however that the bar for new looping control flow constructs is high and that this year's and possibly the next year's roadmap have been centered around "maturity" and so such a proposal might not fit in the roadmap.

For now, there are variants of do while loops documented in this issue. Whether some of these are hacks can be argued about, but they exist at any rate. It seems to me that we could perhaps leave a note in the book or something (cc @steveklabnik) about how one achieves the desired results.

With that said, I think this issue can be closed.

I want the "do ... while" loop also, why the Rust is so reluctant to do it, I have an example of using the "do ... while", and we use much in C, but now I have to make it with "while" statement, since the "loop" is so verbose. If you can abbreviate the function to fn, public to pub not bar, why not an elegant "do ... while"?

The "loop" way

fn read_data() -> io::Result<i32> {
    let mut fp = File::open("/path/to/file")?;
    let mut buf = [0; 4096];
    loop {
        let mut n = fp.read(&mut buf)?;
        println!("read {} bytes", n);
        if n == 0 { 
            break; 
        }
    }
    return Ok(0);
}

Workaround: set a precondition and go with "while" (let mut n = 1)

fn read_data() -> io::Result<i32> {
    let mut fp = File::open("/path/to/file")?;
    let mut buf = [0; 4096];
    let mut n = 1;
    while n > 0 {
        n = fp.read(&mut buf)?;
        println!("read {} bytes", n);
    }
    return Ok(0);
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

3442853561 picture 3442853561  Â·  4Comments

mahkoh picture mahkoh  Â·  3Comments

steveklabnik picture steveklabnik  Â·  4Comments

onelson picture onelson  Â·  3Comments

clarfonthey picture clarfonthey  Â·  3Comments