aij
aij

Reputation: 6471

What is the difference between loop and while true?

The Rust tutorial, and now book claim there is a difference between while true and loop, but that it isn't super important to understand at this stage.

If you need an infinite loop, you may be tempted to write this:

while true {

However, Rust has a dedicated keyword, loop, to handle this case:

loop {

Rust's control-flow analysis treats this construct differently than a while true, since we know that it will always loop. The details of what that means aren't super important to understand at this stage, but in general, the more information we can give to the compiler, the better it can do with safety and code generation, so you should always prefer loop when you plan to loop infinitely.

Having done a little bit of compiler-type work, I have to wonder what possible semantic difference there is, since it would be trivial for the compiler to figure out both are an infinite loop.

So, how does the compiler treat them differently?

Upvotes: 45

Views: 14782

Answers (5)

darkHat07
darkHat07

Reputation: 31

TL;DR loop is an expression; while true is a statement, they have different use cases and offer flexibility and expressiveness.

The main reason is that loop blocks are expressions, whereas while blocks are statements. Differentiating the both and setting distinct rules for each case allows the compiler to make optimizations without sacrificing safety.

While loop and while true loops have the same behavior (and probably similar optimizations—the condition checking at the beginning of each while true loop iteration is dropped for performance), they are still bound to their Rust implementations, and cannot be used interchangeably.

For instance, we can use loop loops like this:

let mut counter = 0;
let result = loop {
    counter += 1;
    if counter == 10 {
        break counter * 2; // Return a value from the loop
    }
};
println!("The result is {}", result); // The result is 20

We can see that we assign the evaluation of the loop expression to result with no problems.

On the other hand, while loops are statements, and so we cannot assign them to variables, etc.

Doing the same with a while true loop would look like this:

let mut counter = 0;
let mut result = 0;
while counter < 10 {
    counter += 1;
    if counter == 10 {
        result = counter * 2; // Assign a value inside the loop
        break;
    }
};
println!("The result is {}", result); // The result is 20

If we tried to implement this simple algorithm using while true in the same way as loop would yield the following compiler error:

let mut counter = 0;
let mut result = while counter < 10 {
    counter += 1;
    if counter == 10 {
        counter * 2; // Statement, hence () is returned
        break
    }
};
println!("The result is {}", result); // `()` cannot be formatted with the default formatter
error[E0277]: `()` doesn't implement `std::fmt::Display`
  --> src/main.rs:10:34
   |
10 |     println!("The result is {}", result); // The result is 20
   |                                  ^^^^^^ `()` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `()`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

Upvotes: 0

user2859458
user2859458

Reputation: 2495

One major difference is that loop can return a value by passing a value to break. while and for will not:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    assert_eq!(result, 20);
}

Upvotes: 33

Peter Hall
Peter Hall

Reputation: 58695

The first thing to say is, in terms of performance, these are likely to be identical. While Rust itself doesn't do anything special with while true, LLVM likely does make that optimisation. The Rust compiler tries to keep things simple by delegating optimisations to LLVM where it can.

in general, the more information we can give to the compiler, the better it can do with safety and code generation

While certain constant expressions might get optimised away by LLVM, the semantics of the language are not altered by whether an expression is constant or not. This is good, because it helps humans reason about code better too.

Just because true is a simple expression, we know it's constant. And so is true != false and [0; 1].len() == 1. But what about num_cpus::get() == 1? I actually don't know if there are some compilation targets where that could be constant, and I shouldn't have to think about it either!

The error in telotortium's example would be more significant when combined with generated code or macros. Imagine a macro which sometimes results in a simple static expression like true == true, but sometimes references a variable or calls a function. Sometimes the compiler is able to ascertain that the loop runs once, but other times it just can't. In Rust right now, the error in that example will always be an error, no matter what code was generated for that condition. No surprises.

Upvotes: 5

Stargateur
Stargateur

Reputation: 26697

What is the difference between loop and while true?

You could ask what is the difference between for and while? The answer will be close to: What is a programming idiom?

When you write while condition {}, you say "while condition is true, do that", but we can see that say "while true is true, do that", is redundant. This is where loop comes from, it can express infinite loops very well because we say "loop on that". We don't have any condition, this is better.

So, how does the compiler treat them differently?

I can't answer the "how" question, but I suppose you want to know "why". It allows the compiler to know that this loop will run at least one time, like the do {} while (condition); from C. The compiler can use this information to produce better code or warnings. Plus, you will be certain that the loop will be executed where a while loop could be gone because the compiler optimize it away. The fun part is that internally Rust uses LLVM, and it looks like LLVM doesn't have a way to express infinite loop, so it produces bugs in some cases.

Upvotes: 0

telotortium
telotortium

Reputation: 3541

This was answered on Reddit. As you said, the compiler could special-case while true, but it doesn't. Since it doesn't, the compiler doesn't semantically infer that an undeclared variable that's set inside a while true loop must always be initialized if you break out of the loop, while it does for a loop loop:

It also helps the compiler reason about the loops, for example

let x;
loop { x = 1; break; }
println!("{}", x)

is perfectly valid, while

let x;
while true { x = 1; break; }
println!("{}", x);

fails to compile with "use of possibly uninitialised variable" pointing to the x in the println. In the second case, the compiler is not detecting that the body of the loop will always run at least once.

(Of course, we could special case the construct while true to act like loop does now. I believe this is what Java does.)

Upvotes: 47

Related Questions