From 420b72b848d3429ba8cde46b76046703d9a4343b Mon Sep 17 00:00:00 2001 From: Paul Crowley Date: Mon, 26 Jun 2017 13:20:36 -0700 Subject: [PATCH 1/7] label-break-value RFC --- text/0000-label-break-value.md | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 text/0000-label-break-value.md diff --git a/text/0000-label-break-value.md b/text/0000-label-break-value.md new file mode 100644 index 00000000000..355cc0d7bed --- /dev/null +++ b/text/0000-label-break-value.md @@ -0,0 +1,101 @@ +- Feature Name: label_break_value +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Allow a break not only out of `loop`, but of labelled blocks with no loop. Like `loop`, this break can carry a value. + +This depends on [RFC 1624](https://github.com/rust-lang/rfcs/blob/master/text/1624-loop-break-value.md) landing. I proposed this [here](https://github.com/rust-lang/rust/issues/37339#issuecomment-306573033). An identical proposal was part of the [explanation for trait based exception handling](https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md#early-exit-from-any-block). + +# 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. + +``` +'block: { + do_thing(); + if condition_not_met() { + break 'block; + } + do_next_thing(); + if condition_not_met() { + break 'block; + } + do_last_thing(); +} +``` +Following RFC 1624, this, like `return`, can also carry a value: +``` +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: +``` +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: +``` +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 +}; +``` +# Detailed design +[design]: #detailed-design +``` +'BLOCK_LABEL: { EXPR } +``` +would simply be syntactic sugar for +``` +'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. + +# 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 + +This feature isn't necessary; 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 types and return values is often not worth it. + +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 label might be used even when it's never referred to. 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. From d4adfa67d480c9a5ca071aafe8640bc737a6d949 Mon Sep 17 00:00:00 2001 From: Paul Crowley Date: Mon, 26 Jun 2017 21:00:43 -0700 Subject: [PATCH 2/7] Fill in start date and link to PR --- text/0000-label-break-value.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-label-break-value.md b/text/0000-label-break-value.md index 355cc0d7bed..dbb9d4247ac 100644 --- a/text/0000-label-break-value.md +++ b/text/0000-label-break-value.md @@ -1,6 +1,6 @@ - Feature Name: label_break_value -- Start Date: (fill me in with today's date, YYYY-MM-DD) -- RFC PR: (leave this empty) +- Start Date: 2017-06-26 +- RFC PR: [#2046](https://github.com/rust-lang/rfcs/pull/2046) - Rust Issue: (leave this empty) # Summary From 9281a8f4a3e2b139f263679768780549d3df80f7 Mon Sep 17 00:00:00 2001 From: Paul Crowley Date: Mon, 26 Jun 2017 21:04:26 -0700 Subject: [PATCH 3/7] Delete superfluous paragraph link to discussions in discussions instead. --- text/0000-label-break-value.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/text/0000-label-break-value.md b/text/0000-label-break-value.md index dbb9d4247ac..f900805a4af 100644 --- a/text/0000-label-break-value.md +++ b/text/0000-label-break-value.md @@ -8,8 +8,6 @@ Allow a break not only out of `loop`, but of labelled blocks with no loop. Like `loop`, this break can carry a value. -This depends on [RFC 1624](https://github.com/rust-lang/rfcs/blob/master/text/1624-loop-break-value.md) landing. I proposed this [here](https://github.com/rust-lang/rust/issues/37339#issuecomment-306573033). An identical proposal was part of the [explanation for trait based exception handling](https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md#early-exit-from-any-block). - # Motivation [motivation]: #motivation From 670c1e0a9a6e2242dd28d9dcfc965965ed9bfda0 Mon Sep 17 00:00:00 2001 From: Paul Crowley Date: Sat, 1 Jul 2017 10:29:36 -0700 Subject: [PATCH 4/7] Fix broken Rust code with proper references Various other small improvements --- text/0000-label-break-value.md | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/text/0000-label-break-value.md b/text/0000-label-break-value.md index f900805a4af..ed3a316bdec 100644 --- a/text/0000-label-break-value.md +++ b/text/0000-label-break-value.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -Allow a break not only out of `loop`, but of labelled blocks with no loop. Like `loop`, this break can carry a value. +Allow a `break` of labelled blocks with no loop, which can carry a value. # Motivation [motivation]: #motivation @@ -26,7 +26,7 @@ In its simplest form, this allows you to terminate a block early, the same way t do_last_thing(); } ``` -Following RFC 1624, this, like `return`, can also carry a value: +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: ``` let result = 'block: { if foo() { break 'block 1; } @@ -37,7 +37,7 @@ let result = 'block: { 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: ``` let result = 'block: { - for v in container.iter() { + for &v in container.iter() { if v > 0 { break 'block v; } } 0 @@ -46,15 +46,35 @@ let result = 'block: { This extension handles searches more complex than loops in the same way: ``` let result = 'block: { - for v in first_container.iter() { + for &v in first_container.iter() { if v > 0 { break 'block v; } } - for v in second_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: +``` +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 ``` @@ -81,7 +101,7 @@ The proposal adds new syntax to blocks, requiring updates to parsers and possibl # Alternatives [alternatives]: #alternatives -This feature isn't necessary; 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 types and return values is often not worth it. +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`. From 8417a1b455cec9eb956c18634d19bb4c99c574ce Mon Sep 17 00:00:00 2001 From: Paul Crowley Date: Fri, 25 Aug 2017 10:58:12 -0700 Subject: [PATCH 5/7] Error message for forbidden unlabelled break Per @pnkfelix's concern, make explicit that the error message the user sees where an unlabelled break is ambiguous suggests labelling a surrounding loop. Also mark all the code blocks as rust for syntax highlighting. --- text/0000-label-break-value.md | 35 +++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/text/0000-label-break-value.md b/text/0000-label-break-value.md index ed3a316bdec..87d3ce1025a 100644 --- a/text/0000-label-break-value.md +++ b/text/0000-label-break-value.md @@ -13,7 +13,7 @@ Allow a `break` of labelled blocks with no loop, which can carry a value. 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() { @@ -27,7 +27,7 @@ In its simplest form, this allows you to terminate a block early, the same way t } ``` 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; } @@ -35,7 +35,7 @@ let result = 'block: { }; ``` 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; } @@ -44,7 +44,7 @@ let result = 'block: { }; ``` 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; } @@ -56,7 +56,7 @@ let result = 'block: { }; ``` Implementing this without a labelled break is much less clear: -``` +```rust let mut result = None; for &v in first_container.iter() { if v > 0 { @@ -77,17 +77,38 @@ 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 is currently forbidden and 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. In these circumstances, 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 From 61455729bf8b78309d042649959aec9155227052 Mon Sep 17 00:00:00 2001 From: Paul Crowley Date: Wed, 6 Sep 2017 07:39:59 -0700 Subject: [PATCH 6/7] Wording tweaks to fix ambiguities --- text/0000-label-break-value.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-label-break-value.md b/text/0000-label-break-value.md index 87d3ce1025a..9b14537561a 100644 --- a/text/0000-label-break-value.md +++ b/text/0000-label-break-value.md @@ -88,7 +88,7 @@ except that unlabelled `break`s or `continue`s which would bind to the implicit 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 is currently forbidden and will produce an error at compile time: +Because the target of the `break` is ambiguous, code like the following will produce an error at compile time: ```rust loop { 'labelled_block: { @@ -98,7 +98,7 @@ loop { } } ``` -If the intended target of the `break` is the surrounding loop, it may not be clear to the user how to express that. In these circumstances, the error message should explicitly suggest labelling the loop so that the `break` can target it. +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: { @@ -132,7 +132,7 @@ We have three options for handling an unlabelled `break` or `continue` inside a - 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 label might be used even when it's never referred to. The third is consistent with unlabelled blocks and with Java, but seems like a rich potential source of confusion. +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 From 4683ec76dd6ed7f4419dc768d1f9d2fd0289f5d4 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 27 Feb 2018 20:40:22 +0100 Subject: [PATCH 7/7] RFC 2046 --- text/0000-label-break-value.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/text/0000-label-break-value.md b/text/0000-label-break-value.md index 9b14537561a..0073d5b8246 100644 --- a/text/0000-label-break-value.md +++ b/text/0000-label-break-value.md @@ -1,7 +1,8 @@ - Feature Name: label_break_value - Start Date: 2017-06-26 -- RFC PR: [#2046](https://github.com/rust-lang/rfcs/pull/2046) -- Rust Issue: (leave this empty) +- 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