What is the difference between a and {a} for if

This is compiles just fine:

fn test() {
    if {33} > 1 {}
}

But if add extra layer of '{', it fails to compile:

fn test() {
    if {{33} > 1} {}
}

I need add () around {33} to fix it.
Why?

When you add the layer of braces, that becomes a block expression, which may contain statements. Block expressions can also be treated as statements even without a semicolon, which is what now happens to your inner {33}. This is always assumed, rather than trying to resolve it contextually by the following tokens. When you add parentheses, it can't be a statement anymore.

Context is sometimes ambiguous even if we did look ahead -- e.g. {{33} - 1} could be interpreted as either { ({33}) - 1 } or { {33}; (-1) }.

4 Likes

Thank you for answer, but how
{{33} - 1} can be interpreted as { {33}; (-1) } ?

In my mental model the compiler parse code as tree,
something like depth-first search, so first go to {33},
parse it from { to } and get result 33, then we have
tree 33 - 1 and this obviously 32, so result of {{33} - 1} is 32.

Why expression statement ambiguity if there are no ";" here?

So if takes an expression as the condition. When you use braces, this expression is a block which can have statements inside it. For example:

fn foo() {}
fn test() -> i32 {
    if {
        foo();
        let local = 4;
        println!("{}", local);
        false
    } {
        42
    } else {
        0
    }
}

Also statements that are blocks can end without a semicolon

fn bar() {
    let mut x = 1;
    {
        let y = 2;
        // ...
    } // y dropped here (if it had a destructor)
    // no semicolon
    x = 2;
    // ...
}

This is why you get problems as soon as you use multiple layers of braces: You have blocks inside blocks, that is, blocks on the left hand side of an infix operator expression in a context where statements are allowed.

For example this compiles:

use std::ops::Neg;
struct S;
impl Neg for S {
    type Output = bool;
    fn neg(self) -> bool {
        true
    }
}
fn foo() {}
fn test() {
    if { {foo()} - S} {}
}

but it is indeed interpreted like

// definition of struct S etc.. repeated here
fn test() {
    if {
        {
            foo()
        } // end of statement
        -S
    } {}
}

Finally - is not <. However it has a higher precedence. So if you wanted

fn bar() -> bool {true}
fn test() {
    if {{ bar() } < true} {}
}

to compile like you would imagined it, it would be a bit too inconsistent with

fn foo() {}
fn test() {
    if {{ foo() } - S < true} {}
}
// interpreted like:
fn test() {
    if {
        {
            foo()
        } // end of statement
        -S < true
    } {}
}

because by using - you would somehow loose stuff on the left hand side of the <.


There is a similar ambiguity with function calls and array indices

let f = |x| x+1;
{ f }(1);

is interpreted as

let f = |x| x+1;
{
    f
} // end of statement
(1); // actually a parenthesized expression

// but this won’t actually compile since f is not of type ().

And .. ranges are also problematic.

The only thing that does work on blocks, as far as I can tell, is the ? operator and method calls.

{ ... }.method()
1 Like

The simple version is that it could be {{foo()} - 1}.

When parsing, we know absolutely nothing about types. If foo() has no return value, then {foo()} is a statement, because you're allowed to omit the semicolon here.

(If I could go back in time, I'd remove these rules for semicolon omission from Rust, because imho they cause more ire than they save effort. It'd look more odd to always have ; after, say, a for loop, but it'd, imho, be more consistent.)

Because the parser cannot possibly know whether the trailing expression of the block is typed at i32, (), or any other type, it just assumes that you want the statement block rather than an expression block and (effectively) inserts a semicolon for you.

1 Like

Here's the actual list of kinds that are parsed as statements without semicolons:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.