-
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
RFC: label-break-value #2046
Merged
Merged
RFC: label-break-value #2046
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
420b72b
label-break-value RFC
ciphergoth d4adfa6
Fill in start date and link to PR
ciphergoth 9281a8f
Delete superfluous paragraph
ciphergoth 670c1e0
Fix broken Rust code with proper references
ciphergoth 8417a1b
Error message for forbidden unlabelled break
ciphergoth 6145572
Wording tweaks to fix ambiguities
ciphergoth 4683ec7
RFC 2046
Centril File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
- Feature Name: label_break_value | ||
- Start Date: 2017-06-26 | ||
- RFC PR: [rust-lang/rfcs#2046](https://github.com/rust-lang/rfcs/pull/2046) | ||
- Rust Issue: [rust-lang/rust#48594](https://github.com/rust-lang/rust/issues/48594) | ||
|
||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Allow a `break` of labelled blocks with no loop, which can carry a value. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
In its simplest form, this allows you to terminate a block early, the same way that `return` allows you to terminate a function early. | ||
|
||
```rust | ||
'block: { | ||
do_thing(); | ||
if condition_not_met() { | ||
break 'block; | ||
} | ||
do_next_thing(); | ||
if condition_not_met() { | ||
break 'block; | ||
} | ||
do_last_thing(); | ||
} | ||
``` | ||
In the same manner as `return` and the labelled loop breaks in [RFC 1624](https://github.com/rust-lang/rfcs/blob/master/text/1624-loop-break-value.md), this `break` can carry a value: | ||
```rust | ||
let result = 'block: { | ||
if foo() { break 'block 1; } | ||
if bar() { break 'block 2; } | ||
3 | ||
}; | ||
``` | ||
RFC 1624 opted not to allow options to be returned from `for` or `while` loops, since no good option could be found for the syntax, and it was hard to do it in a natural way. This proposal gives us a natural way to handle such loops with no changes to their syntax: | ||
```rust | ||
let result = 'block: { | ||
for &v in container.iter() { | ||
if v > 0 { break 'block v; } | ||
} | ||
0 | ||
}; | ||
``` | ||
This extension handles searches more complex than loops in the same way: | ||
```rust | ||
let result = 'block: { | ||
for &v in first_container.iter() { | ||
if v > 0 { break 'block v; } | ||
} | ||
for &v in second_container.iter() { | ||
if v < 0 { break 'block v; } | ||
} | ||
0 | ||
}; | ||
``` | ||
Implementing this without a labelled break is much less clear: | ||
```rust | ||
let mut result = None; | ||
for &v in first_container.iter() { | ||
if v > 0 { | ||
result = Some(v); | ||
break; | ||
} | ||
} | ||
if result.is_none() { | ||
for &v in second_container.iter() { | ||
if v < 0 { | ||
result = Some(v); | ||
break; | ||
} | ||
} | ||
} | ||
let result = result.unwrap_or(0); | ||
``` | ||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
```rust | ||
'BLOCK_LABEL: { EXPR } | ||
``` | ||
would simply be syntactic sugar for | ||
```rust | ||
'BLOCK_LABEL: loop { break { EXPR } } | ||
``` | ||
except that unlabelled `break`s or `continue`s which would bind to the implicit `loop` are forbidden inside the *EXPR*. | ||
|
||
This is perhaps not a conceptually simpler thing, but it has the advantage that all of the wrinkles are already well understood as a result of the work that went into RFC 1624. If *EXPR* contains explicit `break` statements as well as the implicit one, the compiler must be able to infer a single concrete type from the expressions in all of these `break` statements, including the whole of *EXPR*; this concrete type will be the type of the expression that the labelled block represents. | ||
|
||
Because the target of the `break` is ambiguous, code like the following will produce an error at compile time: | ||
```rust | ||
loop { | ||
'labelled_block: { | ||
if condition() { | ||
break; | ||
} | ||
} | ||
} | ||
``` | ||
If the intended target of the `break` is the surrounding loop, it may not be clear to the user how to express that. Where there is a surrounding loop, the error message should explicitly suggest labelling the loop so that the `break` can target it. | ||
```rust | ||
'loop_label: loop { | ||
'labelled_block: { | ||
if condition() { | ||
break 'loop_label; | ||
} | ||
} | ||
} | ||
``` | ||
|
||
# How We Teach This | ||
[how-we-teach-this]: #how-we-teach-this | ||
|
||
This can be taught alongside loop-based examples of labelled breaks. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
The proposal adds new syntax to blocks, requiring updates to parsers and possibly syntax highlighters. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
Everything that can be done with this feature can be done without it. However in my own code, I often find myself breaking something out into a function simply in order to return early, and the accompanying verbosity of passing parameters and return values with full type signatures is a real cost. | ||
|
||
Another alternative would be to revisit one of the proposals to add syntax to `for` and `while`. | ||
|
||
We have three options for handling an unlabelled `break` or `continue` inside a labelled block: | ||
|
||
- compile error on both `break` and `continue` | ||
- bind `break` to the labelled block, compile error on `continue` | ||
- bind `break` and `continue` through the labelled block to a containing `loop`/`while`/`for` | ||
|
||
This RFC chooses the first option since it's the most conservative, in that it would be possible to switch to a different behaviour later without breaking working programs. The second is the simplest, but makes a large difference between labelled and unlabelled blocks, and means that a program might label a block without ever explicitly referring to that label just for this change in behavior. The third is consistent with unlabelled blocks and with Java, but seems like a rich potential source of confusion. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
None outstanding that I know about. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this also be written?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or, for that matter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They use different
find
functions, actuallyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withoutboats: That's a lot neater than the iterator based solution I proposed.
joshtriplett: the find condition is different for the two iterators.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And if you don't like
or_else
chains:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, all these closure-based solutions have trouble if there is a
return
or?
also inside the loop.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@withoutboats @ciphergoth 🤦, sorry, I missed that.