The "loop" keyword doesn't carry its weight


#1

I don’t understand why a so much clean language as Rust has the “loop” keyword. At first when I’ve read the explanation of the “loop” keyword I assumed there was some subtle purpose I didn’t get. Now I think there is no subtle purpose beside the two obvious ones.

In the following program the foo2() doesn’t compile, but a compiler should be able to understand that “while true{}” is an infinite loops as much as “loop{}”:

fn foo1(mut n: u32) -> u32 {
    loop {
        n += 1;
        if n > 10 {
            return n;
        }
    }
}

fn foo2(mut n: u32) -> u32 {
    while true {
        n += 1;
        if n > 10 {
            return n;
        }
    }
}

fn main() {}

So I suggest to deprecate the “loop” keyword, and later (Rust 2.0?) remove it.


#2

I personally think loop is clearer than while true. Also most constructs are desugared into some loop+match combination.


#3

Rust is designed as almost a “bare bones” language. It lacks a do-while, and tons of other things you find in D/C++/etc. In a such squeaky clean language the presence of “loop” because it “looks cleaner” looks like a post-rationalization. Take a look at Rust constructors or other ton of things that “look less cleaner” because Rust designers have preferred to keep Rust very lean & clean… If you want a less lean language then remove the “loop” and give me a do-while instead. That’s way more useful than a “loop”.

(Edited, to reduce emphasis).

You’re free to keep “loop” in the post-processed language if you want, and remove “loop” from the surface language. Or you can replace the “loop” with a “while true” in the post-processed language and remove “loop” from both layers of the language.


#4

I don’t see any problem with it. Personally, I prefer to use loop keyword when needed instead of while true


#5

“loop” is essentially useless, while other useful things like do-while, or better struct constructors or many other things are missing, probably because Rust designers prefers minimalism. It’s like a mole in the face of a young pale white woman :slight_smile:


#6

I’m not sure that’s a fitting analogy.

There are quite a lot of places where Rust is a rather rich language, so just the case that better struct constructors are missing isn’t really a good point. If Rust were minimal, it wouldn’t have the where syntax for example and some others.


#7

To fully make this explicit, we have if, if let, while let, for, while and loop + match.

I personally don’t see the lack of do { .. } while _; as problematic; it comes up less often than you’d think (and I say this coming from Java, where I have about 5 times more usual while loops than do-whiles in my code).

Also @leonardo to reiterate my argument, I think that loop { .. } makes it abundantly clear that we have a loop that will be either exited via return or break or run forever. A while x { .. } where x happens to be always true can thus easily be linted against.

Finally, I think the converse to your argument is much more convincing: removing loop doesn’t give us enough benefit to pull the weight of a breaking change.


#8

This was proposed in November of 2014 and we decided not to go in this direction: https://github.com/rust-lang/rfcs/pull/429


#9

That’s pretty much the “sufficiently advanced compiler” argument.

And besides, there’s intention: what if the value is based on platform-specific constants?

Suddenly, your code will stop passing borrowck depending on the target you’re compiling for and you didn’t even ask for it.

loop is more fundamental than while anyway, I would rather remove while (and keep for as it’s slightly more complex and useful sugar), but it has its uses.


#10

What’s the idiomatic correct way to translate this D code to Rust?

void main() {
    bool cond1, cond2, cond3, cond4;
    do {
        if (cond1) continue;
        if (cond2) continue;
        if (cond3) continue;
    } while (cond4);
}

#11
extern crate rand;
fn main() {
    if rand::random::<f64>() < 0.25f64 { loop {} }
}

… what? You never initialised the conditions! Ok, ok… I assume it’d be something like…

fn main() {
    loop {
        if cond1() { continue; }
        if cond2() { continue; }
        if cond3() { continue; }
        if !cond4() { break; }
    }
}

Or thereabouts, probably. while true might be an acceptable substitute in this situation as a signal that “this isn’t supposed to loop forever”, but I’m not sure if there are any deeper consequences to that.

Personally, I prefer do { ... } while or something analogous, but oh well.


#12

In D bools are initialized to false, despite I think the D specs say you should not use uninitialized variables…

Every continue skips the last if, so it’s not correct code, I think.


#13

Aah, I forgot about that. In that case, it’s just fn main() {} :stuck_out_tongue:

Oh bum. I clearly didn’t think this through… Hmm…

macro_rules! do_while {
    ($body:block while $cond:expr) => {
        {
            let mut first = true;
            loop {
                if !first && !$cond { break }
                first = false;
                $body
            }
        }
    };
}

fn main() {
    let cond1 = false;
    let cond2 = false;
    let cond3 = false;
    let cond4 = false;
    do_while! {{
        if cond1 { continue; }
        if cond2 { continue; }
        if cond3 { continue; }
    } while cond4 }
}

Yes, I do try to solve every problem with macros.


#14

Yeah! [redacted] But it shows why I’d trade loop{} for something like do{}while :slight_smile:


#15

There’s no need for that, thank you very much.


#16

You say that because this is a serious forum and nearly no jokes are allowed, or because what I have written could be offensive? Daniel Keep is a friend, and surely I didn’t mean to offend him.

(I ask because I am not native English speaker, and sometimes I may write jokes that send the wrong message, that’s why I usually avoid them. But “cool boy” should be a fun appellative…).


#17

Well, to me it sounded dismissive. If that wasn’t intended, so much the better.


#18

[Moderator note: Jokes are fine, but calling people silly/fun names can be easy to misinterpret as an insult as we’ve seen here, especially across language barriers, so let’s stay away from that. Also, using standards of female beauty as analogies for beautiful code is something I’d like to discourage. I’m sure no offense was intended; just keep it in mind for the future.]


#19

With my moderator hat off: Here’s one concrete advantage of an explicit loop syntax.

The following code (playground) is valid Rust, because the compiler knows that x must be initialized before the loop exits:

    let x;
    loop {
        let rand = random_number();
        if rand > 5 {
            x = rand;
            break;
        }
    }
    println!("{}", x);

If you change loop to while true, the program is no longer valid (playground):

error: use of possibly uninitialized variable: x [E0381]

While we could change the compiler to recognize while true as a special case, it would be impossible to catch every possible way of writing an infinite loop (e.g. for _ in repeat(()), or while !false). Compilers and analyzers for other languages struggle with this in practice; for example a C++ compiler might produce a warning for while true but not for while 1 or for (;;). In Rust there is exactly one explicit way to write an infinite loop, so there’s no need for programmers or compilers to learn a bunch of special cases.


#20

The “infinite loop detector” doesn’t need to be perfect, it’s enough for it to be conservative. If it gives an error, then the programmer fixes the code until the compiler is able to see it’s an infinite loop.

And I think a programmer that wants to write an infinite loop is going to use a very small number of variants of code. So they will not write “for _ in repeat(())” to write an infinite loop, so we can ignore this case.

I’d like the Rust compiler to recognize this is an infinite loop and to not give the error message in this case. The infinite loop recognizer is also going to be useful to spot unwanted situations where the code has an infinite loop, that is where the code has a bug. The D compiler has such recognizer.

This D code compiles with no errors:

int random_number() { return 7; }

int bar() {
    while (true) {
        auto rand = random_number();
        if (rand > 5) {
            return rand;
        }
    }
}

void main() {}

While this:

int random_number() { return 7; }
bool foo() { return true; }

int bar() {
    while (foo()) {
        auto rand = random_number();
        if (rand > 5) {
            return rand;
        }
    }
}

void main() {}

Gives an error because the D compiler can’t be sure that the function returns a value in all paths:

test.d(5,5): Error: function test.bar no return exp; or assert(0); at end of function

In D assert(0) is special, it’s like the unreachable!() of Rust.