Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC: label-break-value #2046
Conversation
ciphergoth
changed the title
label-break-value RFC
RFC: label-break-value
Jun 26, 2017
This comment has been minimized.
This comment has been minimized.
|
This feels to much like a goto. I don't really want Rust == Fortran |
This comment has been minimized.
This comment has been minimized.
|
@mark-i-m Is there something particular about this proposal that makes it feel too goto-like? This seems to have the same restrictions as the labeled break we already have, as well as to normal break and return: cannot go backwards, cannot skip variable definitions, can only exit a syntactically-visible |
This comment has been minimized.
This comment has been minimized.
|
The problems with |
This comment has been minimized.
This comment has been minimized.
This is a fair point, but I feel more strongly, I guess. I personally don't like the labeled breaks we already have. IMHO, they usually just make control flow harder to follow/write/debug without really providing any benefit, which is the heart of Dijkstra's point. In fact, I would go so far as to say, that I generally dislike normal break/continue, too, but I accept them for lack of a better alternative. The way I see it, the more entry/exit points you have for a loop, the worse code quality is -- it's just becomes convoluted to reason about loop invariants. |
This comment has been minimized.
This comment has been minimized.
|
I should add that by breaking out of the middle of a block, you have effictively made it into 2 blocks, one of which doesn't always happen. And that is not always syntactically obvious, IMHO... |
This comment has been minimized.
This comment has been minimized.
|
Full text of Go To Statement Considered Harmful - very interesting! |
This comment has been minimized.
This comment has been minimized.
|
I filed a Pre-RFC about this, see also discussion there. |
This comment has been minimized.
This comment has been minimized.
|
Other discussions: I proposed this here. An identical proposal was part of the explanation for trait based exception handling. |
This comment has been minimized.
This comment has been minimized.
golddranks
commented
Jun 27, 2017
•
|
I think that making control flow more flexible is generally a good thing. As already argued by others Dijkstra's critique doesn't apply here; the critique is against obfuscating the program state using surprising control paths. In the context of this feature it's only allowed to break outwards from a scope, which doesn't allow witnessing uninitialised variables or skipping in the middle of some code that expects there to be a state set up by the earlier code. It does allow skipping some code in a possibly "surprising way" the sense that one can jump from an inner scope to the grandparent scope, but unlike exceptions, this is still a local feature that is well visible in the local context – so in the end, it hardly isn't actually surprising, and when used with discretion, can lead to cleaner code. I'd argue, that without flexible and safe control flow constructs, people tend to store the "which path" information to local flags or use inner functions to be able to return early. These both feel like hacks to me. The problem with manually juggling control flow flags is that the compiler can't hardly reason about their state and the problem with functions is that they are a wrong abstraction – they come with a new stack frame and aren't easily able to access the parent frame unless the state is explicitly passed to them. They are too heavyweight. Having labelled breaks is a nice way to retain the "state machine-y" feeling of a function-local control flow but still allow more flexible flow that comes handy from time to time. |
This comment has been minimized.
This comment has been minimized.
|
@ciphergoth In fact my first instinct for desugaring |
This comment has been minimized.
This comment has been minimized.
|
@eddyb The RFC that proposes In the pre-RFC discussion, nikomatsakis says:
|
withoutboats
added
the
T-lang
label
Jun 27, 2017
This comment has been minimized.
This comment has been minimized.
Perhaps I will always just disagree on this... I suspect I am probably more extreme than most on this point. It looks like I am pretty outnumbered here, so I wont spam everyone more beyond this post, unless asked for more Basically, I can't imagine many useful situations where this is easier to follow 'block: {
do_thing();
if condition_not_met() {
break 'block;
}
do_next_thing();
if condition_not_met() {
break 'block;
}
do_last_thing();
}than this do_thing();
if condition_met() {
do_next_thing();
if condition_met() {
do_last_thing();
}
}In the first example it's not clear that the preconditions for But in the second one, the curly braces (and formatting conventions) make it clear, which is what we expect because curly braces are the primary way rust indicates a block of code. Of course someone will argue that if you have 50 of them, you will have too much indenting. I thinks it's worth it, but I think that's really a matter of taste. |
This comment has been minimized.
This comment has been minimized.
|
@mark-i-m In my use case, I need to conditionally break from inside a deeply nested structure, the Also, I especially like the pattern to break early if some condition is not met, so that there is no big rightward drift. |
This comment has been minimized.
This comment has been minimized.
|
@est31 I'll take your word for it that it is hard to refactor (I haven't tried). I guess I can see the use case, but I still don't really like the break as a pattern... although, I don't have an alternative, other than major refactoring... |
This comment has been minimized.
This comment has been minimized.
|
@mark-i-m I actually agree with your for that example. Overall, though, I suspect that people won't reach for this except in cases where The example I found quite compelling was this one: 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
}Because a simple translation to normal constructs ends up something like this: 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);Which I find more awkward, as it obscures the symmetry and has the compiler less able to help with the initialization logic. (You could also do this one with the |
This comment has been minimized.
This comment has been minimized.
|
Should I be changing the examples in the RFC to reflect discussion here? |
This comment has been minimized.
This comment has been minimized.
|
I think it would be useful to include some of the motivating examples. And I would also like to see the disadvantages section updated, with some of the objections, even if the language is not strongly worded... |
This comment has been minimized.
This comment has been minimized.
|
For the specific example given you could also do this: first_container.iter().filter(|&&v| v > 0).chain(
second_container.iter().filter(|&&v| v < 0)
).map(|&v| v).next().unwrap_or(0); |
This comment has been minimized.
This comment has been minimized.
|
I won't lie. I'm pretty sure most of these examples can be expressed more
elegantly with some refactoring, but it might take some effort...
…On Jul 1, 2017 2:05 PM, "Paul Crowley" ***@***.***> wrote:
For the specific example given you could also do this:
first_container.iter().filter(|&&v| v > 0).chain(
second_container.iter().filter(|&&v| v < 0)
).map(|&v| v).next().unwrap_or(0);
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2046 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AIazwKQtyYLkV7CG1drVxECCQmN8cGJzks5sJop3gaJpZM4OFz9D>
.
|
This comment has been minimized.
This comment has been minimized.
|
Big fan of this; thanks for writing! One nit is while the desugaring is correct, I don't think in long term we should implement/teach/think about the feature that way. I rather have:
RFC language can be incremental I suppose, but o hope something like that makes the books. |
This comment has been minimized.
This comment has been minimized.
|
Ericson2314 - how do we integrate Maybe we could describe loops as being implicitly this: 'outer: {
for i in container.iter() {
'inner: {
LOOP BODY
}
}
}Then |
nikomatsakis
assigned
nikomatsakis and
scottmcm
and unassigned
nikomatsakis
Jul 6, 2017
This comment has been minimized.
This comment has been minimized.
|
@alercah Can you take the examples given in the RFC and mock up equivalents using the |
This comment has been minimized.
This comment has been minimized.
|
I was thinking something like (note that I'm using let i = 'label: do catch {
if cond {
break 'label 3;
}
// ...
val()
}It's a little bit more verbose, but if you make for i in 1..10 {
'label: do catch {
if cond {
break;
}
}
}This would be much less dangerous for misinterpretation. |
This comment has been minimized.
This comment has been minimized.
|
(I should add: I'm not convinced the idea is better by any means. It was just idle musing.) |
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Feb 24, 2018
|
The final comment period is now complete. |
Centril
referenced this pull request
Feb 27, 2018
Open
Tracking issue for RFC 2046, label-break-value #48594
Centril
merged commit 417f92d
into
rust-lang:master
Feb 27, 2018
This comment has been minimized.
This comment has been minimized.
|
Huzzah! The RFC is merged! Tracking issue: rust-lang/rust#48594 |
This comment has been minimized.
This comment has been minimized.
|
So we just added goto in rust. I wish I saw this RFC sooner to comment against it but too late... :-/ |
This comment has been minimized.
This comment has been minimized.
Honestly, I don't think that is a fair description =) |
This comment has been minimized.
This comment has been minimized.
rpjohnst
commented
Feb 28, 2018
|
One of the reasons goto makes things harder is because it allows for irreducible control flow. Not only does this make reading the code harder for humans, but it makes the compiler's job more complicated. Fortunately, label-break-value does not allow for irreducible control flow. It's hardly any different from early returns, just applied at the expression level. |
This comment has been minimized.
This comment has been minimized.
Needless to say, not everyone agrees, but it is done already, and the debate has already been had... |
This comment has been minimized.
This comment has been minimized.
rpjohnst
commented
Feb 28, 2018
|
The point I'm making is not a matter of opinion- label-break-value does nothing to the control flow graph that early returns don't already do, especially when you consider inlining. You can argue the subjective costs and benefits to humans of it all day long, which is why I thought I'd point out an objective way to look at it. |
This comment has been minimized.
This comment has been minimized.
|
Previously, I was emulating the feature with a |
This comment has been minimized.
This comment has been minimized.
|
@GuillaumeGomez See related previous discussion above: #2046 (comment) |
This comment has been minimized.
This comment has been minimized.
SoniEx2
commented
Mar 1, 2018
|
if statements are glorified gotos. it doesn't matter what language they're in. here's a lua example because I'm too lazy to write a rust example when I can use an existing example. https://gist.github.com/SoniEx2/fc5d3614614e4e3fe131/#file-special-lua-L11-L71 labeled blocks are just IIFEs on steroids. |
This comment has been minimized.
This comment has been minimized.
|
Yes I saw it, I commented after. I just don't like the feature, but it's nothing more than an opinion in a sea of opinions. I just wanted to comment and so did I. :) |
This comment has been minimized.
This comment has been minimized.
|
I think the question is inherently a bit subjective... Does this encourage less-clear-than-it-could-be control flow? (as opposed to an objective question: does this fundamentally introduce new control flow?) Anyway, I think that the best thing to do now would be to focus on making the feature as good as it can be |
This comment has been minimized.
This comment has been minimized.
SoniEx2
commented
Mar 2, 2018
•
I mean, it allows replacing: loop {
/* ... */
if cond { break val; }
/* ... */
break otherval;
}with: 'block: {
/* ... */
if cond { break /*'block?*/ val; }
/* ... */
val
}So I mean, if you find the former clearer... ;) |
ciphergoth commentedJun 26, 2017
•
edited by Centril
Rendered
Tracking issue: rust-lang/rust#48594
Allow a break not only out of
loop, but of labelled blocks with no loop. Likeloop, this break can carry a value.See also Pre-RFC discussion.
Status as of 2018-01-18.