Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If-else could allow omitting braces on the else #1616

Closed
glaebhoerl opened this issue May 14, 2016 · 15 comments
Closed

If-else could allow omitting braces on the else #1616

glaebhoerl opened this issue May 14, 2016 · 15 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@glaebhoerl
Copy link
Contributor

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:

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

    Could instead be this:

    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.

@durka
Copy link
Contributor

durka commented May 14, 2016

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.

@gereeter
Copy link

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.

@birkenfeld
Copy link

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));

@wycats
Copy link
Contributor

wycats commented Jun 19, 2016

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.

@letheed
Copy link

letheed commented Jun 23, 2016

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.

@Ericson2314
Copy link
Contributor

+1 else match.

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

@leoyvens
Copy link

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

@burdges
Copy link

burdges commented Jun 25, 2016

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

@dashed
Copy link

dashed commented Jun 29, 2016

👍 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.

@letheed
Copy link

letheed commented Jun 29, 2016

@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.

@phoenixenero
Copy link

phoenixenero commented Jul 25, 2016

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.

@leksak
Copy link

leksak commented Aug 4, 2016

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.

@strega-nil
Copy link

I wouldn't be opposed for specifically match.

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label Aug 4, 2016
@scottmcm
Copy link
Member

scottmcm commented Feb 4, 2018

An RFC that proposed this but ended up closed: #1712 (comment)

@glaebhoerl
Copy link
Contributor Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests