Rfcs: If-else could allow omitting braces on the else

Created on 14 May 2016  ·  15Comments  ·  Source: rust-lang/rfcs

Background

As we all know, Rust doesn't require parentheses around the condition of an if, but does require braces around the bodies of if and else.

_This is good_ because it avoids the ambiguity and potential for mistakes around nested ifs and elses that exists in other C-family languages.

The problematic example is this:

if (foo())
    if (bar())
        baz();
else quux(); // which `if` does this belong to?

In current Rust, that example must look like this:

if foo() {
    if bar() {
        baz();
    }
} else { // aha! it belongs to the first one
    quux();
}

Or like this:

if foo() {
    if bar() {
        baz();
    } else { // or in this case, the second one
        quux();
    }
}

In either case, the meaning is now explicit and unambiguous.


The idea

But as far as I can tell, only the first pair of braces are in fact required to accomplish this.

If we only put braces on the if:

if foo() {
    if bar() {
        baz();
    }
} else quux();
if foo() {
    if bar() {
        baz();
    } else quux();
}

It's still just as explicit.


Advantages

The potential benefits of relaxing this would be:

  • We could avoid a proliferation of braces and indentation when the else block consists entirely of another construct delimited by braces, such as a match. For example, this:

rust if foo() { do_this() } else { match bar() { A => do_that(), B => do_the_other() } }

Could instead be this:

rust if foo() { do_this() } else match bar() { A => do_that(), B => do_the_other() }

  • As a special case of the above, we _currently_ allow writing if...else if...else chains "naturally", as opposed to requiring else { if {... by simply giving special treatment to if else. This would now "fall out" of the general rule, and would no longer require special casing.

Drawbacks

  • It's a bit less symmetric.
  • There'd still be the hazard that you could accidentally write:

    if foo() {
     ...
    } else
     bar();
     baz();
    

    and imagine that the baz() is in the else, when it's actually not (a la goto fail).

    A slightly more sophisticated rule that avoids this hazard would be that you may omit the braces around the body of an else _if the body is a single expression which itself requires braces_. So you could write else match { ... }, else for { ... }, else loop { ... }, else if { ... }, and so on, but not else bar() or else quux().


Evaluation

In my estimation, in terms of theoretical elegance getting to remove the if else special case would roughly cancel out with breaking the if ... { ... } else { ... } symmetry, leaving the practical benefit of fewer braces and less rightward drift, which is a net win.

T-lang

Most helpful comment

I feel this is a loss of symmetry for very little gain. The second drawback shows that this opens up Rust to goto fail bugs that the current rule is designed to avoid.

The "more sophisticated" alternative (allow any braced block in the else clause) is more acceptable to me, but I don't see a big need for it.

All 15 comments

I feel this is a loss of symmetry for very little gain. The second drawback shows that this opens up Rust to goto fail bugs that the current rule is designed to avoid.

The "more sophisticated" alternative (allow any braced block in the else clause) is more acceptable to me, but I don't see a big need for it.

I'd be in support of the "more sophisticated" alternative - I've definitely wanted else match before. The other things (else for, else loop, etc.) don't seem useful to me, but it seems cleanest to do the more general rule, while still preventing goto fail style bugs.

That said, since I only really want else match and everything here can be done incrementally and backwards compatibly, my favorite proposal at this time would probably be just adding else match.

To try else match out, it should be as easy as

--- a/src/libsyntax/parse/parser.rs
+++ b/src/libsyntax/parse/parser.rs
@@ -3258,6 +3258,8 @@ impl<'a> Parser<'a> {
     pub fn parse_else_expr(&mut self) -> PResult<'a, P<Expr>> {
         if self.eat_keyword(keywords::If) {
             return self.parse_if_expr(None);
+        } else if self.eat_keyword(keywords::Match) {
+            return self.parse_match_expr(None);
         } else {
             let blk = self.parse_block()?;
             return Ok(self.mk_expr(blk.span.lo, blk.span.hi, ExprKind::Block(blk), None));

I'd be in support of the "more sophisticated" alternative - I've definitely wanted else match before.

Me too, but I'm not sure I like the idea of else for x in y {. I'm not sure I can put my finger on why else for x in y { feels worse than else if x {.

I think it has something to do with how many expressions you need to process before hitting the {, as well as the fact that else if is common (and else match feels similar to that control flow). This makes else for feel like a weird exception to the normal nesting.

I don't think general omission is a good idea. Being able to write:

if cond {
    foo();
}
else bar;
baz;

is begging for bugs to happen. Way too ambiguous.

I like the idea of being able to write this though:

if cond {
    foo();
}
else match x {
    pat => bar(),
    _   => {},
}

I've found myself trying to write that a few times before. Rust already has a lot of brackets and visual noise, and match has a nasty tendency to rightwards drift. As long as it's unambiguous, I think it would improve readability.

Rust being a functional language, it would make sense that match x { } could be accepted anywhere a scope { } is accepted.

+1 else match.

I wouldn't mind a if EXPR then EXPR else EXPR but I don't think that would get much traction.

Making else match valid gives the intuition that else match { ... } else { ... } is also valid. This can be confusing for a newcomer.

I'd hope that match { ... } else { ... } would not be valid?

👍 for else match { ... }. To compromise on ambiguity, else match would only be used as the last chain in if-else.

If this syntactic sugar is concisely elaborated in its own RFC, I'll happily 👍 it.

@burdges no it would not, that wouldn't make any sense. I proposed that brackets directly surrounding a match statement can be eliminated because { match x { ... } } is equivalent to match x { ... }. So in my mind, you could have:

if cond1 {
    ...
}
else if cond2 match x {
    ...
}
else match y {
   ...
}

It's just a bit of syntactic sugar to reduce the noise of extra brackets and remove one level of indentations.

By the way, this kind of stuff already exists in a small form and pretty much every programmer writing in a C-style language has already written it.

The else if clause.

Writing

if foo() {
    do_this()
} else if bar() {
    do_that()
} else if baz() {
    do_this_other_thing();
}

is the same as writing

if foo() {
    do_this()
} else {
    if bar() {
        do_that()
    } else {
        if baz() {
            do_this_other_thing();
        }
    }
}

Of course we don't have a match ... else ... or for ... else ... but if we did it would be very similar to how else if works.

I am vehemently opposed to this addition for the drawback that you describe,

if foo() {
   ...
} else
   bar();
   baz();

as others have pointed out this is just begging for bugs. The omission of braces do cause bugs in everything from student submissions to production-code. I'd prefer if we didn't invite it into the Rust language.

I wouldn't be opposed for specifically match.

An RFC that proposed this but ended up closed: https://github.com/rust-lang/rfcs/pull/1712#issuecomment-279573396

Thinking I'm gonna close this too to reduce clutter :) (if anyone disagrees, feel free to reopen)

Was this page helpful?
0 / 5 - 0 ratings