Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support "do while" loops #1313

Closed
mdinger opened this issue Oct 8, 2015 · 41 comments
Closed

Support "do while" loops #1313

mdinger opened this issue Oct 8, 2015 · 41 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@mdinger
Copy link
Contributor

mdinger commented Oct 8, 2015

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.

@dscorbett
Copy link

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

while {
    do_work();
    condition
} { }

@mdinger
Copy link
Contributor Author

mdinger commented Oct 8, 2015

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

@seanmonstar
Copy link
Contributor

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
#1313 (comment).

@steveklabnik
Copy link
Member

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

@mdinger
Copy link
Contributor Author

mdinger commented Oct 8, 2015

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.

@seanmonstar
Copy link
Contributor

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
#1313 (comment).

@ghost
Copy link

ghost commented Oct 9, 2015

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);

@mdinger
Copy link
Contributor Author

mdinger commented Oct 9, 2015

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();
}

@BurntSushi
Copy link
Member

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...

@ghost
Copy link

ghost commented Oct 10, 2015

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.

@mdinger
Copy link
Contributor Author

mdinger commented Oct 10, 2015

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.

@petrochenkov
Copy link
Contributor

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!)

@mdinger
Copy link
Contributor Author

mdinger commented Oct 10, 2015

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.

@bluss
Copy link
Member

bluss commented Nov 3, 2015

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.

@ticki
Copy link
Contributor

ticki commented Nov 20, 2015

@petrochenkov: What's wrong with

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

?

@petrochenkov
Copy link
Contributor

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

@brendanzab
Copy link
Member

👎

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

@DanielKeep
Copy link

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
    }
}

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label Aug 19, 2016
@snuk182
Copy link

snuk182 commented Sep 17, 2016

Wouldn't it work with while let ?

@ticki
Copy link
Contributor

ticki commented Sep 17, 2016

@snuk182 while let serves a different purpose.

@sinesc
Copy link

sinesc commented Oct 29, 2016

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".

@ticki
Copy link
Contributor

ticki commented Oct 29, 2016

@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.

@ticki
Copy link
Contributor

ticki commented Oct 29, 2016

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

@sinesc
Copy link

sinesc commented Oct 29, 2016

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.

@jaroslaw-weber
Copy link

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

@Lucretiel
Copy link

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.

@Xpyder
Copy link

Xpyder commented Jul 6, 2018

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 (^-^)

@Lucretiel
Copy link

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.

@Lucretiel
Copy link

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

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

@Xpyder
Copy link

Xpyder commented Jul 6, 2018

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

@erg
Copy link

erg commented Apr 26, 2019

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();
}

@Lucretiel
Copy link

Lucretiel commented Apr 26, 2019

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).

@erg
Copy link

erg commented Apr 26, 2019

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

@Lucretiel
Copy link

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.

@comex
Copy link

comex commented Apr 27, 2019

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.

@nickolay
Copy link

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

The macro solution from #1313 (comment) 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 rust-lang/rustfmt#2636 (comment) I don't have much hope it would be fixed anytime soon.)

@arieroos
Copy link

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

@ryanpeach
Copy link

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?

@RustyYato
Copy link

RustyYato commented Nov 14, 2019

@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.

@Centril
Copy link
Contributor

Centril commented Nov 14, 2019

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.

@sweihub
Copy link

sweihub commented May 21, 2020

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);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests