Understanding if loop is an expression

Hello,

I'm a beginner, reading by the book the documentation. Manual describes the expression concept, introducing the removal of ; on last line to act the return of this particular line result.

Nonetheless, few chapters after, documentation show a loop example this way :

    let result = loop {
        counter += 1;

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

I fully understand the last ;, which states the assignation to result var. But IMHO, we should write break counter * 2 instead of break counter * 2;, shouldn't we? What's puzzling me is both solutions compile and returns result.

Can someone elaborate on this one please? Is it due to the break usage syntax?

Side question, wouldn't have been logical to allow this following syntax (just texted it did not compile)?

    let result = loop {
        counter += 1;

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

Thanks

1 Like

The body of an if is a block. In a block, the semicolon after the last statement (here, the only one) is not obligatory if it is an expression statement:

  • if you include the semicolon, the block will have type () (unit)
  • if you omit the semicolon, the block will yield the last expression, with the type of whatever the expression is.

Now a break expression has type ! (never), because it causes control flow to diverge. Therefore, it can coerce to any type, including (), which is the type of an if without an else. Therefore it doesn't matter if you include the semicolon: either you do, causing the block to explicitly have type (), or you don't, causing the ! type of break to coerce to () anyway.

Most definitely not. break causes the loop to exit. Nothing in counter * 2 affects control flow, it's a pure expression.

4 Likes

This expression

break counter * 2

imparts flow control, but the expression itself has the never type !. That type can coerce to any other type.

This conditional if block (with no else block):

if counter == 10 { /* ... */ }

must evaluate to () (as this what the absent else case results in).

Both inner blocks can satisfy that.

// inherently ()
if counter == 10 { break counter * 2; }
// ! coerces to ()
if counter == 10 { break counter * 2 }

The trailing expression of a loop must be () since it signifies infinite-looping-until-explicit-escape. If you could implicitly break a loop with a trailing expression, there would have to be some sort of special casing around ()-typed loops, or they would always break at the end. Or more generally, why loop if you're going to unconditionally break at the end of the loop?

So this doesn't work because it's expecting a ().[1]

loop { 0 }

On top of that, in your example, the trailing expression isn't counter * 2. It's

if counter == 10 { counter * 2 }

The only way for that to have a non-() type is to supply the else, say:

// `continue` is `!` and can coerce to your integer type
if counter == 10 { counter * 2 } else { continue }

But that's an error for the same reason loop { 0 } is an error.


  1. This remains true no matter the type of the loop expression. ↩ī¸Ž

6 Likes

Thanks for pointing on the never type.