-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Statement-ize Looping Forms #955
Conversation
So right now we have the following: { loop { break } - 3 } // ==> -3
{ (loop { break } - 3) } // type error
{ if false {} else {} - 3 } // ==> -3
{ (if false {} else {} - 3) } // type error
{ { } - 3 } // ==> 3
{ ({ } - 3) } // type error See http://is.gd/zSjrKp. If so, I would keep the existing symmetry and just mandate a final semicolon for a block that ends in a loop. Temporarily it's weird, but long run it is the most consistent by treating all "cury brace expressions" the same. I suppose I'd like even more to get rid of the parenthesis rules on all of these and just require more semi colons, but that would break more code. |
@Ericson2314 I'm sorry, I cannot parse your sentence
(Did you mean to have an "I" in there, as in "I would"?) I need a more concrete elaboration of what alternative you are proposing; I honestly cannot infer what it is from what you have written here. |
@pnkfelix I am sorry, both for the typo and the lack of clarity. I did mean "I would" (edited for posterity). What I am proposing is disallowing ending a block with a loop, unless the loop is followed by a semicolon. This gives us: { loop { break } - 3 } // ==> -3
{ (loop { break } - 3) } // type error
{ loop { break } } // syntax error for now
{ loop { break }; } // ==> ()
{ if false {} else {} - 3 } // ==> -3
{ (if false {} else {} - 3) } // type error
{ if false { 3 } else { 3 } } // ==> 3
{ if false { 3 } else { 3 }; } // ==> ()
{ { } - 3 } // ==> -3
{ ({ } - 3) } // type error
{ { 3 } } // ==> 3
{ { 3 }; } // ==> () Which can then become backwards compatibly: { loop { break } - 3 } // ==> -3
{ (loop { break } - 3) } // type error
{ loop { break 3 } } // ==> 3
{ loop { break 3 }; } // ==> ()
{ if false {} else {} - 3 } // ==> -3
{ (if false {} else {} - 3) } // type error
{ if false { 3 } else { 3 } } // ==> 3
{ if false { 3 } else { 3 }; } // ==> ()
{ { } - 3 } // ==> -3
{ ({ } - 3) } // type error
{ { 3 } } // ==> 3
{ { 3 }; } // ==> () |
@Ericson2314 I will add that to alternatives. I can see the appeal, yet it would (IMO) make a lot of code a teensy bit uglier. |
Thanks! |
I am not keen on requiring more semicolons than we require now -- seems like we could add these extensions backwards compatbility even without this, though it would mean that while/for loops with a "break with value" would have type That said, I am unpersuaded as to the need for the original feature itself -- I agree it makes for elegant code in some cases to |
(To be clear, there are other options, such as a required |
This feels a bit odd to me. Without requiring semicolons, it feels inconsistent with the rest of the language. And requiring semicolons is ugly. As near as I can tell, the only thing this accomplishes is allowing looping constructs to always evaluate to a non- |
I'm in favor of the sentiment as futureproofing. The phrase "then we probably will require the use of parentheses around such forms when they appear as the tail expression of a block" is worrying since putting the expression-loop in tail position might be a common case for the feature. |
@kballard The idea isn't that loops always have a non-() type in expression position, but that absent of a concrete plan it is only safe they do for compatibility. Also it is better to think of the loops in "statement position" as expressions with their value thrown away, rather than a "true statement" like a let binding. That way, the type of the loop itself is context-free. |
Overall I still trend negative on this proposal, because I think the scheme we have is working pretty well and the need to change it is small. But I just re-read the RFC briefly and realized I was slightly confused about the distinction between the alternatives and main proposal. Unfortunately, now that I grok it better, I feel like there is a fundamental conflict between C-like and expression-like usage that makes me dislike the idea of In particular, (iiuc) the RFC is saying that a looping control-flow construct cannot be in the tail expression of a block. That is, in the following program: fn foo() {
while something { ...; break; ... }
} the This implies that in the future, if fn bar(i: i32) -> Option<i32> {
while something(i) { if something_else(i) { break i; } ... }
} Instead one would be required to write On the other hand, if we adopted the alternative that @Ericson2314 proposed], then we would find that the fallout would be much larger, because we would need a This conflict seems to me to be somewhat fundamental though, and seems to suggest to me that making Is there a flaw in this reasoning? I guess the flaw might be that we want both |
In re-reading @brson's comment I realize he put my entire post into 1 sentence. ;) |
Okay, so far it sounds like core team may be trending towards the second listed alternative:
I'll keep this RFC up for a while to see if anyone comes in with strong arguments against that view. Followup note: In all honesty, I tend to agree with both the commentary from @brson and the analysis from @nikomatsakis I have only one counter-argument against this point from @brson (and its quite a weak counter) :
A similar situation of requiring a parenthesis can arise in (However, that example is definitely a strawman, constructed solely to illustrate a corner case, that I do not expect to see perhaps ever in practice. @brson's note wins out because of the key phrase: "common case".) |
For what it's worth (which, I acknowledge, probably isn't much), I am in favor of requiring semicolons on the end of looping expressions. I disagree with the aesthetic concern about requiring new semicolons: I actually consider it more elegant, and more revealing of programmer intention, to require semicolons after loops than to treat the braces from loops (and if statements) specially. And I think we are currently treating loops specially:
Requiring a semicolon after a loop statement reveals programmer intention that the value returned by the looping expression is (currently, until loops can return non-unit values) meaningless. Regarding the appendix, it is surprising that those two |
While I do prefer the semicolon route, Here's an alternative plan that is fully backwards compatible: First some context, I have two guiding principles:
So lets add:
All our existing
and thus keep their type from today. |
@Ericson2314 I know you already said this new alternative plan is fully backwards compatible, but just to be 100% clear: This RFC was meant to address future-proofing concerns to accommodate #352 / #961 . If I understand what you said properly, you are advocating (or at least outlining) a plan that requires no changes from Rust as it stands today; i.e. no future proofing, right? I only ask to try to clarify whether you are suggesting that we can indeed close this RFC. |
For the record, a third plan is to make it so that the for- and while- loops without explicit breaks return () too. Since there is only one way to exit those loops, I don't see any useful information could be returned. But I'd still like |
For what it's worth, my preference is (and has been all along) for the approach where So:
with |
@glaebhoerl Ah, I initially thought of else rules as "functional break requires else", which breaks the So in sum, if the core team changes their minds and decides semicolons are OK after all, then I like that and eventually arriving at: given x:T, y: U
loop { }: !
loop { break }: ()
loop { break x }: T
while foo { }: ()
while foo { break }: Option<()>
while foo { break x }: Option<T>
for _ in for { }: ()
for _ in for { break }: Option<()>
for _ in for { break x }: Option<T>
while let PAT = y { }: U // y if it doesn't match the pattern
while let PAT = y { break }: Result<(), U>
while let PAT = y { break x }: Result<T, U> If semicolons are no-go, I like @glaebhoerl's plan (but what do you do with |
Requiring semicolons seems a non-starter since other C-like languages don't and it would be extremely surprising for programmers (most likely, expletives would be uttered upon discovering this rule). Doing nothing and in the future making "break" and "break ()" do different things instead seems completely fine and perhaps the best option, and might actually be good as it emphasizes the fact that the behavior of "break ()" is something new compared to current C-like languages (where for/while is not an expression), and also emphasizes that the programmer wants to know whether the loop was exited by breaking or not. |
I like @glaebhoerl 's plan too, because it doesn't change the meaning of existing loops, but merely extends them in a consistent fashion. I also prefer the "else" syntax to using "Option", as it makes the common use-case clear and simple: for x in xs {
if x.foo == y { break x; }
} else {
// Handle missing case
} While of course the same thing can be achieve with "Option", it requires either an additional "if" or "match" statement, or a closure. Using "else" just feels leaner and more straightforward, even though it is techincally introducing new syntax. |
@bill-myers Thank you for putting your case against semicolons so plainly. I disagree because brace-blocks mean something completely different in Rust than they do in other C-like languages: in other languages, they are used to group statements. In Rust, a brace-block is generally an expression. Expressions are generally terminated with semicolons. Requiring a semicolon makes this fundamental difference in brace-usage actually appear different in code, which I think is to the good. If semicolons were required, then a programmer used to C who forgets a semicolon on the end of a while-loop would probably utter an expletive. But that would be followed by an "aha" moment when they understand what's going on, and the language's behavior and design would become more clear as a result. That is, the confusion is brief, and isolated to developers learning the language for the first time (and who should, as such, be expecting to learn new concepts as they try the language out). On the other hand, if semicolons are not required to terminate statements, then when you run into situations like @pnkfelix described in the appendix to this RFC (showing an ambiguity between an if statement and an if expression), it would be confusing even to intermediate Rust developers. There is no initial confusion or expletive uttered when working with the language for the first time, but on the other hand, that allows a possibly mistaken interpretation of brace-expressions to become ingrained in the reader's mind, so that the expletive is uttered much later, more rarely, but with more force. To make this more concrete, I'd advocate for a rule that all statements should be terminated with semicolons. Braces in most Rust code blocks denote expressions. Expressions and statements should be unambiguous. |
(i am no longer a proponent of doing this, if i ever was ...; closing.) |
To be clear, is just the stopped gap ruled out now / are the backwards compatible plans still on the table for post 1.0? |
@Ericson2314 the hypothetical feature of allowing loops to return values is still a potential future feature, at least in the sense that #961 remains open (just postponed for post-1.0). The particular detail of one kind of future proofing proposed by this RFC #955, however, is no longer on the table. (Or at least, I am no longer pushing for it, and it is quite unlikely to get put into 1.0.) |
Gotcha---didn't realize a postponement issue was made when the original RFC was closed. |
Restrict grammar of Rust language for 1.0 so that all looping syntactic forms (
for
,loop
,while
, andwhile let
) are statements (instead of expressions). Forms likelet d = loop { };
and(while foo() { })
all produce errors at parse time.Rendered draft
Spawned off of discussion of RFC #352