From a3316b957d96001507e57fcab2d8edd90e54db8d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 20 May 2016 16:08:35 +0100 Subject: [PATCH 01/12] loop-break-value RFC (see #961) --- text/0000-loop-break-value.md | 161 ++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 text/0000-loop-break-value.md diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md new file mode 100644 index 00000000000..f659e517d19 --- /dev/null +++ b/text/0000-loop-break-value.md @@ -0,0 +1,161 @@ +- Feature Name: loop_break_value +- Start Date: 2016-05-20 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Let a `loop { ... }` expression return a value via `break my_value;`. + +# Motivation +[motivation]: #motivation + +This pattern is currently hard to implement without resorting to a function or +closure wrapping the loop: + +```rust +fn f() { + let outcome = loop { + // get and process some input, e.g. from the user or from a list of + // files + let result = get_result(); + + if successful() { + break result; + } + // otherwise keep trying + }; + + use_the_result(outcome); +} +``` + +In some cases, one can simply move `use_the_result(outcome)` into the loop, but +sometimes this is undesirable and sometimes impossible due to lifetimes. + +# Detailed design +[design]: #detailed-design + +This proposal does two things: let `break` take a value, and let `loop` have a +result type other than `()`. + +### break syntax + +Four forms of `break` will be supported: + +1. `break;` +2. `break 'label;` +3. `break EXPR;` +4. `break 'label EXPR;` + +where `'label` is the name of a looping construct and `EXPR` is an evaluable +expression. + +### result type + +Currently the result-type of a 'loop' without 'break' is `!` (never returns), +which may be coerced to `()`. This is important since a loop may appear as +the last expression of a function: + +```rust +fn f() { + loop { + do_something(); + // never breaks + } +} +fn g() -> () { + loop { + do_something(); + if Q() { break; } + } +} +fn h() -> ! { + loop { + do_something(); + // this loop is not allowed to break due to `!` result type + } +} +``` + +This proposal changes the result type to `T`, where: + +* a loop which is never "broken" via `break` has result-type `!` (coercible to `()`) +* a loop's return type may be deduced from its context, e.g. `let x: T = loop { ... };` +* where a loop is "broken" via `break;` or `break 'label;`, its result type is `()` +* where a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, `EXPR` must evaluate to type `T` + +It is an error if these types do not agree. Examples: + +```rust +// error: loop type must be ! or () not i32 +let a: i32 = loop {}; +// error: loop type must be i32 and must be &str +let b: i32 = loop { break "I am not an integer."; }; +// error: loop type must be Option<_> and must be &str +let c = loop { + if Q() { + "answer" + } else { + None + } +}; +fn z() -> ! { + // function does not return + // error: loop may break (same behaviour as before) + loop { + if Q() { break; } + } +} +``` + +### result value + +A loop only yields a value if broken via some form of `break ...;` statement, +in which case it yields the value resulting from the evaulation of the +statement's expression (`EXPR` above), or `()` if there is no `EXPR` +expression. + +Examples: + +```rust +assert_eq!(loop { break; }, ()); +assert_eq!(loop { break 5; }, 5); +let x = 'a loop { + 'b loop { + break 'a 1; + } + break 'a 2; +}; +assert_eq!(x, 1); +``` + +# Drawbacks +[drawbacks]: #drawbacks + +The proposal changes the syntax of `break` statements, requiring updates to +parsers and possibly syntax highlighters. + +The type of `loop` expressions is no longer fixed and cannot be explicitly +typed. + +# Alternatives +[alternatives]: #alternatives + +No alternatives to the design have been suggested. It has been suggested that +the feature itself is unnecessary, and indeed much Rust code already exists +without it, however the pattern solves some cases which are difficult to handle +otherwise and allows more flexibility in code layout. + +# Unresolved questions +[unresolved]: #unresolved-questions + +It would be possible to allow `for`, `while` and `while let` expressions return +values in a similar way; however, these expressions may also terminate +"naturally" (not via break), and no consensus has been reached on how the +result value should be determined in this case, or even the result type. +It is thus proposed not to change these expressions at this time. + +It should be noted that `for`, `while` and `while let` can all be emulated via +`loop`, so perhaps allowing the former to return values is less important. From 7fda498e3db5c6a1d343647c5f19d4ceb55f6870 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 20 May 2016 16:23:22 +0100 Subject: [PATCH 02/12] Reference existing work --- text/0000-loop-break-value.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index f659e517d19..1f38940800a 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -6,6 +6,10 @@ # Summary [summary]: #summary +(This is a result of discussion of issue #961 and related to RFCs +[352](https://github.com/rust-lang/rfcs/pull/352) and +[955](https://github.com/rust-lang/rfcs/pull/955).) + Let a `loop { ... }` expression return a value via `break my_value;`. # Motivation @@ -159,3 +163,5 @@ It is thus proposed not to change these expressions at this time. It should be noted that `for`, `while` and `while let` can all be emulated via `loop`, so perhaps allowing the former to return values is less important. + +See discussion of #961 for more on this topic. From 75b82f4c00f76b3bae2d67c07176cb1ba271f8a7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 20 May 2016 16:24:37 +0100 Subject: [PATCH 03/12] Link issue since document is in a different repo --- text/0000-loop-break-value.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index 1f38940800a..b2ea7569bc7 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -6,7 +6,8 @@ # Summary [summary]: #summary -(This is a result of discussion of issue #961 and related to RFCs +(This is a result of discussion of +[issue #961](https://github.com/rust-lang/rfcs/issues/961) and related to RFCs [352](https://github.com/rust-lang/rfcs/pull/352) and [955](https://github.com/rust-lang/rfcs/pull/955).) @@ -164,4 +165,5 @@ It is thus proposed not to change these expressions at this time. It should be noted that `for`, `while` and `while let` can all be emulated via `loop`, so perhaps allowing the former to return values is less important. -See discussion of #961 for more on this topic. +See [discussion of #961](https://github.com/rust-lang/rfcs/issues/961) +for more on this topic. From b7ca757a78651a310ca532672f54537eeb69feab Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 20 May 2016 08:39:01 -0700 Subject: [PATCH 04/12] A few suggestions (feel free to reject! :)) I can't make line edits without an open PR, so I did this instead. --- text/0000-loop-break-value.md | 47 ++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index b2ea7569bc7..e5e53d0b83d 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -43,9 +43,9 @@ sometimes this is undesirable and sometimes impossible due to lifetimes. [design]: #detailed-design This proposal does two things: let `break` take a value, and let `loop` have a -result type other than `()`. +type other than `()`. -### break syntax +## Break Syntax Four forms of `break` will be supported: @@ -54,12 +54,11 @@ Four forms of `break` will be supported: 3. `break EXPR;` 4. `break 'label EXPR;` -where `'label` is the name of a looping construct and `EXPR` is an evaluable -expression. +where `'label` is the name of a loop and `EXPR` is an expression. -### result type +### Type of Loop -Currently the result-type of a 'loop' without 'break' is `!` (never returns), +Currently the type of a 'loop' without 'break' is `!` (never returns), which may be coerced to `()`. This is important since a loop may appear as the last expression of a function: @@ -79,17 +78,17 @@ fn g() -> () { fn h() -> ! { loop { do_something(); - // this loop is not allowed to break due to `!` result type + // this loop is not allowed to break due to inferred `!` type } } ``` -This proposal changes the result type to `T`, where: +This proposal changes the type to `T`, where: -* a loop which is never "broken" via `break` has result-type `!` (coercible to `()`) -* a loop's return type may be deduced from its context, e.g. `let x: T = loop { ... };` -* where a loop is "broken" via `break;` or `break 'label;`, its result type is `()` -* where a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, `EXPR` must evaluate to type `T` +* a loop which is never "broken" via `break` has type `!` (which is coercible to anything, as today) +* where a loop is "broken" via `break;` or `break 'label;`, its type is `()` +* where a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, and `EXPR: T`, the loop has type `T` +* all "breaks" out of of a loop must have the same type It is an error if these types do not agree. Examples: @@ -100,7 +99,7 @@ let a: i32 = loop {}; let b: i32 = loop { break "I am not an integer."; }; // error: loop type must be Option<_> and must be &str let c = loop { - if Q() { + break if Q() { "answer" } else { None @@ -115,14 +114,14 @@ fn z() -> ! { } ``` -### result value +## Result Value A loop only yields a value if broken via some form of `break ...;` statement, in which case it yields the value resulting from the evaulation of the statement's expression (`EXPR` above), or `()` if there is no `EXPR` expression. -Examples: +## Examples ```rust assert_eq!(loop { break; }, ()); @@ -135,6 +134,24 @@ let x = 'a loop { }; assert_eq!(x, 1); ``` +```rust +fn y() -> () { + loop { + if coin_flip() { + break; + } else { + break (); + } + } +} +``` +```rust +fn z() -> ! { + loop { + break panic!(); + } +} +``` # Drawbacks [drawbacks]: #drawbacks From 57a00c09829ec31bd7b7e918465c192398e2f19f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 20 May 2016 16:52:35 +0100 Subject: [PATCH 05/12] Address comments, specifically from glaebhoerl --- text/0000-loop-break-value.md | 50 ++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index b2ea7569bc7..1ea3033af00 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -59,8 +59,9 @@ expression. ### result type -Currently the result-type of a 'loop' without 'break' is `!` (never returns), -which may be coerced to `()`. This is important since a loop may appear as +Currently the result-type of a 'loop' without 'break' is `!` (never returns, +and may be coerced to any type), and the result type of a 'loop' with 'break' +is `()`. This is important since a loop may appear as the last expression of a function: ```rust @@ -86,24 +87,24 @@ fn h() -> ! { This proposal changes the result type to `T`, where: -* a loop which is never "broken" via `break` has result-type `!` (coercible to `()`) -* a loop's return type may be deduced from its context, e.g. `let x: T = loop { ... };` +* a loop which is never "broken" via `break` has result-type `!` (coercible) * where a loop is "broken" via `break;` or `break 'label;`, its result type is `()` * where a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, `EXPR` must evaluate to type `T` +* a loop's return type may be deduced from its context, e.g. `let x: T = loop { ... };` It is an error if these types do not agree. Examples: ```rust -// error: loop type must be ! or () not i32 -let a: i32 = loop {}; +// error: loop type must be () and must be i32 +let a: i32 = loop { break; }; // error: loop type must be i32 and must be &str let b: i32 = loop { break "I am not an integer."; }; // error: loop type must be Option<_> and must be &str let c = loop { if Q() { - "answer" + break "answer"; } else { - None + break None; } }; fn z() -> ! { @@ -115,6 +116,19 @@ fn z() -> ! { } ``` +Where a loop does not break, the return type is coercible: + +```rust +fn f() -> () { + // ! coerces to () + loop {} +} +fn g() -> u32 { + // ! coerces to u32 + loop {} +} +``` + ### result value A loop only yields a value if broken via some form of `break ...;` statement, @@ -142,9 +156,6 @@ assert_eq!(x, 1); The proposal changes the syntax of `break` statements, requiring updates to parsers and possibly syntax highlighters. -The type of `loop` expressions is no longer fixed and cannot be explicitly -typed. - # Alternatives [alternatives]: #alternatives @@ -164,6 +175,21 @@ It is thus proposed not to change these expressions at this time. It should be noted that `for`, `while` and `while let` can all be emulated via `loop`, so perhaps allowing the former to return values is less important. +Alternatively, a new keyword such as `default` or `else` could be used to +specify the other exit value as in: + +```rust +fn first(list: Iterator) -> Option { + for x in list { + break Some(x); + } default { + None + } +} +``` -See [discussion of #961](https://github.com/rust-lang/rfcs/issues/961) +The exact syntax is disputed. It is suggested that this RFC should not be +blocked on this issue since break-with-value can still be implemented in the +manner above after this RFC. See the +[discussion of #961](https://github.com/rust-lang/rfcs/issues/961) for more on this topic. From ba6b0cc181023eb0833d08194b8b512c330b37b9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 20 May 2016 17:19:48 +0100 Subject: [PATCH 06/12] Changes resulting from Ericson2314's observations, in particular `break panic!();` --- text/0000-loop-break-value.md | 56 +++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index cc80ad64886..8237c391203 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -84,14 +84,17 @@ fn h() -> ! { } ``` -This proposal changes the type to `T`, where: +This proposal changes the result type of 'loop' to `T`, where: -* a loop which is never "broken" via `break` has result-type `!` (which is coercible to anything, as of today) -* where a loop is "broken" via `break;` or `break 'label;`, its result type is `()` -* where a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, `EXPR` must evaluate to type `T` -* a loop's return type may be deduced from its context, e.g. `let x: T = loop { ... };` +* if a loop is "broken" via `break;` or `break 'label;`, the loop's result type must be `()` +* if a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, `EXPR` must evaluate to type `T` +* as a special case, if a loop is "broken" via `break EXPR;` or `break 'label EXPR;` where `EXPR` evaluates to type `!` (does not return), this does not place a constraint on the type of the loop +* if external constaint on the loop's result type exist (e.g. `let x: S = loop { ... };`), then `T` must be coercible to this type -It is an error if these types do not agree. Examples: +It is an error if these types do not agree or if the compiler's type deduction +rules do not yield a concrete type. + +Examples of errors: ```rust // error: loop type must be () and must be i32 @@ -115,7 +118,7 @@ fn z() -> ! { } ``` -Where a loop does not break, the return type is coercible: +Examples involving `!`: ```rust fn f() -> () { @@ -126,6 +129,25 @@ fn g() -> u32 { // ! coerces to u32 loop {} } +fn z() -> ! { + loop { + break panic!(); + } +} +``` + +Example showing the equivalence of `break;` and `break ();`: + +```rust +fn y() -> () { + loop { + if coin_flip() { + break; + } else { + break (); + } + } +} ``` ### Result value @@ -135,7 +157,7 @@ in which case it yields the value resulting from the evaulation of the statement's expression (`EXPR` above), or `()` if there is no `EXPR` expression. -## Examples +Examples: ```rust assert_eq!(loop { break; }, ()); @@ -148,24 +170,6 @@ let x = 'a loop { }; assert_eq!(x, 1); ``` -```rust -fn y() -> () { - loop { - if coin_flip() { - break; - } else { - break (); - } - } -} -``` -```rust -fn z() -> ! { - loop { - break panic!(); - } -} -``` # Drawbacks [drawbacks]: #drawbacks From 36517812becfc395f346020f91efeab4598afe44 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 20 May 2016 20:06:51 +0100 Subject: [PATCH 07/12] Require break expr to converge --- text/0000-loop-break-value.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index 8237c391203..33df7c2f157 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -54,7 +54,7 @@ Four forms of `break` will be supported: 3. `break EXPR;` 4. `break 'label EXPR;` -where `'label` is the name of a loop and `EXPR` is an expression. +where `'label` is the name of a loop and `EXPR` is an converging expression. ### Result type of loop @@ -88,7 +88,6 @@ This proposal changes the result type of 'loop' to `T`, where: * if a loop is "broken" via `break;` or `break 'label;`, the loop's result type must be `()` * if a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, `EXPR` must evaluate to type `T` -* as a special case, if a loop is "broken" via `break EXPR;` or `break 'label EXPR;` where `EXPR` evaluates to type `!` (does not return), this does not place a constraint on the type of the loop * if external constaint on the loop's result type exist (e.g. `let x: S = loop { ... };`), then `T` must be coercible to this type It is an error if these types do not agree or if the compiler's type deduction @@ -129,11 +128,6 @@ fn g() -> u32 { // ! coerces to u32 loop {} } -fn z() -> ! { - loop { - break panic!(); - } -} ``` Example showing the equivalence of `break;` and `break ();`: From 5254944e7db9b968193f806e8fc919b4e6d1142c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 23 May 2016 11:37:13 +0100 Subject: [PATCH 08/12] loop-break-value: update motivation --- text/0000-loop-break-value.md | 66 +++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index 33df7c2f157..7b3462c5227 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -16,28 +16,58 @@ Let a `loop { ... }` expression return a value via `break my_value;`. # Motivation [motivation]: #motivation -This pattern is currently hard to implement without resorting to a function or -closure wrapping the loop: +> Rust is an expression-oriented language. Currently loop constructs don't +> provide any useful value as expressions, they are run only for their +> side-effects. But there clearly is a "natural-looking", practical case, +> described in [this thread](https://github.com/rust-lang/rfcs/issues/961) +> and [this] RFC, where the loop expressions could have +> meaningful values. I feel that not allowing that case runs against the +> expression-oriented conciseness of Rust. +> [comment by golddranks](https://github.com/rust-lang/rfcs/issues/961#issuecomment-220820787) + +Some examples which can be much more concisely written with this RFC: ```rust -fn f() { - let outcome = loop { - // get and process some input, e.g. from the user or from a list of - // files - let result = get_result(); - - if successful() { - break result; +// without break-with-value: +let x = { + let temp_bar; + loop { + ... + if ... { + temp_bar = bar; + break; } - // otherwise keep trying - }; - - use_the_result(outcome); -} -``` + } + foo(temp_bar) +}; + +// with break-with-value: +let x = foo(loop { + ... + if ... { break bar; } + }); -In some cases, one can simply move `use_the_result(outcome)` into the loop, but -sometimes this is undesirable and sometimes impossible due to lifetimes. +// without break-with-value: +let computation = { + let result; + loop { + if let Some(r) = self.do_something() { + result = r; + break; + } + } + result.do_computation() +}; +self.use(computation); + +// with break-with-value: +let computation = loop { + if let Some(r) = self.do_something() { + break r; + } + }.do_computation(); +self.use(computation); +``` # Detailed design [design]: #detailed-design From 894ae7c80781ab1049ec3e330a74690056f9e1ed Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 23 May 2016 11:48:14 +0100 Subject: [PATCH 09/12] loop-break-value: improve consitency and update syntax for for loops --- text/0000-loop-break-value.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index 7b3462c5227..b420beb6f65 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -28,7 +28,7 @@ Let a `loop { ... }` expression return a value via `break my_value;`. Some examples which can be much more concisely written with this RFC: ```rust -// without break-with-value: +// without loop-break-value: let x = { let temp_bar; loop { @@ -41,13 +41,13 @@ let x = { foo(temp_bar) }; -// with break-with-value: +// with loop-break-value: let x = foo(loop { ... if ... { break bar; } }); -// without break-with-value: +// without loop-break-value: let computation = { let result; loop { @@ -60,7 +60,7 @@ let computation = { }; self.use(computation); -// with break-with-value: +// with loop-break-value: let computation = loop { if let Some(r) = self.do_something() { break r; @@ -88,7 +88,7 @@ where `'label` is the name of a loop and `EXPR` is an converging expression. ### Result type of loop -Currently the result-type of a 'loop' without 'break' is `!` (never returns), +Currently the result type of a 'loop' without 'break' is `!` (never returns), which may be coerced to any type), and the result type of a 'loop' with 'break' is `()`. This is important since a loop may appear as the last expression of a function: @@ -220,21 +220,21 @@ It is thus proposed not to change these expressions at this time. It should be noted that `for`, `while` and `while let` can all be emulated via `loop`, so perhaps allowing the former to return values is less important. -Alternatively, a new keyword such as `default` or `else` could be used to +Alternatively, a keyword such as `else default` could be used to specify the other exit value as in: ```rust fn first(list: Iterator) -> Option { for x in list { break Some(x); - } default { + } else default { None } } ``` -The exact syntax is disputed. It is suggested that this RFC should not be -blocked on this issue since break-with-value can still be implemented in the -manner above after this RFC. See the -[discussion of #961](https://github.com/rust-lang/rfcs/issues/961) -for more on this topic. +The exact syntax is disputed; (JelteF has some suggestions which should work +without infinite parser lookahead) +[https://github.com/rust-lang/rfcs/issues/961#issuecomment-220728894]. +It is suggested that this RFC should not be blocked on this issue since +loop-break-value can still be implemented in the manner above after this RFC. From d6e15942febeb45a8fd2d6aac5c95054105e18a1 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 26 May 2016 12:50:56 +0100 Subject: [PATCH 10/12] Allow break values to diverge again This reverts commit 36517812becfc395f346020f91efeab4598afe44. --- text/0000-loop-break-value.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index b420beb6f65..761b6b5128e 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -84,7 +84,7 @@ Four forms of `break` will be supported: 3. `break EXPR;` 4. `break 'label EXPR;` -where `'label` is the name of a loop and `EXPR` is an converging expression. +where `'label` is the name of a loop and `EXPR` is an expression. ### Result type of loop @@ -118,6 +118,7 @@ This proposal changes the result type of 'loop' to `T`, where: * if a loop is "broken" via `break;` or `break 'label;`, the loop's result type must be `()` * if a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, `EXPR` must evaluate to type `T` +* as a special case, if a loop is "broken" via `break EXPR;` or `break 'label EXPR;` where `EXPR` evaluates to type `!` (does not return), this does not place a constraint on the type of the loop * if external constaint on the loop's result type exist (e.g. `let x: S = loop { ... };`), then `T` must be coercible to this type It is an error if these types do not agree or if the compiler's type deduction @@ -158,6 +159,11 @@ fn g() -> u32 { // ! coerces to u32 loop {} } +fn z() -> ! { + loop { + break panic!(); + } +} ``` Example showing the equivalence of `break;` and `break ();`: From 32bdf90b8266c2c50ec3e79c6c8e87d95fb22028 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 26 May 2016 13:13:32 +0100 Subject: [PATCH 11/12] Add discussion of for/while/while let --- text/0000-loop-break-value.md | 72 ++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index 761b6b5128e..2e28a0c20dd 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -218,16 +218,50 @@ otherwise and allows more flexibility in code layout. # Unresolved questions [unresolved]: #unresolved-questions -It would be possible to allow `for`, `while` and `while let` expressions return -values in a similar way; however, these expressions may also terminate -"naturally" (not via break), and no consensus has been reached on how the -result value should be determined in this case, or even the result type. -It is thus proposed not to change these expressions at this time. +### Extension to for, while, while let -It should be noted that `for`, `while` and `while let` can all be emulated via -`loop`, so perhaps allowing the former to return values is less important. -Alternatively, a keyword such as `else default` could be used to -specify the other exit value as in: +A frequently discussed issue is extension of this concept to allow `for`, +`while` and `while let` expressions to return values in a similar way. There is +however a complication: these expressions may also terminate "naturally" (not +via break), and no consensus has been reached on how the result value should +be determined in this case, or even the result type. + +There are three options: + +1. Do not adjust `for`, `while` or `while let` at this time +2. Adjust these control structures to return an `Option`, returning `None` + in the default case +3. Specify the default return value via some extra syntax + +#### Via `Option` + +Unfortunately, option (2) is not possible to implement cleanly without breaking +a lot of existing code: many functions use one of these control structures in +tail position, where the current "value" of the expression, `()`, is implicitly +used: + +```rust +// function returns `()` +fn print_my_values(v: &Vec) { + for x in v { + println!("Value: {}", x); + } + // loop exits with `()` which is implicitly "returned" from the function +} +``` + +Two variations of option (2) are possible: + +* Only adjust the control structures where they contain a `break EXPR;` or + `break 'label EXPR;` statement. This may work but would necessitate that + `break;` and `break ();` mean different things. +* As a special case, make `break ();` return `()` instead of `Some(())`, + while for other values `break x;` returns `Some(x)`. + +#### Via extra syntax for the default value + +Several syntaxes have been proposed for how a control structure's default value +is set. For example: ```rust fn first(list: Iterator) -> Option { @@ -239,8 +273,18 @@ fn first(list: Iterator) -> Option { } ``` -The exact syntax is disputed; (JelteF has some suggestions which should work -without infinite parser lookahead) -[https://github.com/rust-lang/rfcs/issues/961#issuecomment-220728894]. -It is suggested that this RFC should not be blocked on this issue since -loop-break-value can still be implemented in the manner above after this RFC. +or: + +```rust +let x = for thing in things default "nope" { + if thing.valid() { break "found it!"; } +} +``` + +There are two things to bear in mind when considering new syntax: + +* It is undesirable to add a new keyword to the list of Rust's keywords +* It is strongly desirable that unbounded lookahead is required while syntax + parsing Rust code + +For more discussion on this topic, see [issue #961](https://github.com/rust-lang/rfcs/issues/961). From e9495fb7b7eac30e1e3421a002cfd03bf4ff1363 Mon Sep 17 00:00:00 2001 From: Simonas Kazlauskas Date: Wed, 5 Oct 2016 15:45:12 +0300 Subject: [PATCH 12/12] Refine rough edges wrt coercion --- text/0000-loop-break-value.md | 85 ++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/text/0000-loop-break-value.md b/text/0000-loop-break-value.md index 2e28a0c20dd..be853f2e959 100644 --- a/text/0000-loop-break-value.md +++ b/text/0000-loop-break-value.md @@ -84,14 +84,15 @@ Four forms of `break` will be supported: 3. `break EXPR;` 4. `break 'label EXPR;` -where `'label` is the name of a loop and `EXPR` is an expression. +where `'label` is the name of a loop and `EXPR` is an expression. `break` and `break 'label` become +equivalent to `break ()` and `break 'label ()` respectively. ### Result type of loop Currently the result type of a 'loop' without 'break' is `!` (never returns), -which may be coerced to any type), and the result type of a 'loop' with 'break' -is `()`. This is important since a loop may appear as -the last expression of a function: +which may be coerced to any type. The result type of a 'loop' with a 'break' +is `()`. This is important since a loop may appear as the last expression of +a function: ```rust fn f() { @@ -109,20 +110,30 @@ fn g() -> () { fn h() -> ! { loop { do_something(); - // this loop is not allowed to break due to inferred `!` type + // this loop must diverge for the function to typecheck } } ``` -This proposal changes the result type of 'loop' to `T`, where: - -* if a loop is "broken" via `break;` or `break 'label;`, the loop's result type must be `()` -* if a loop is "broken" via `break EXPR;` or `break 'label EXPR;`, `EXPR` must evaluate to type `T` -* as a special case, if a loop is "broken" via `break EXPR;` or `break 'label EXPR;` where `EXPR` evaluates to type `!` (does not return), this does not place a constraint on the type of the loop -* if external constaint on the loop's result type exist (e.g. `let x: S = loop { ... };`), then `T` must be coercible to this type - -It is an error if these types do not agree or if the compiler's type deduction -rules do not yield a concrete type. +This proposal allows 'loop' expression to be of any type `T`, following the same typing and +inference rules that are applicable to other expressions in the language. Type of `EXPR` in every +`break EXPR` and `break 'label EXPR` must be coercible to the type of the loop the `EXPR` appears +in. + + + +It is an error if these types do not agree or if the compiler's type deduction rules do not yield a +concrete type. Examples of errors: @@ -148,24 +159,6 @@ fn z() -> ! { } ``` -Examples involving `!`: - -```rust -fn f() -> () { - // ! coerces to () - loop {} -} -fn g() -> u32 { - // ! coerces to u32 - loop {} -} -fn z() -> ! { - loop { - break panic!(); - } -} -``` - Example showing the equivalence of `break;` and `break ();`: ```rust @@ -180,6 +173,34 @@ fn y() -> () { } ``` +Coercion examples: + +```rust +// ! coerces to any type +loop {}: (); +loop {}: u32; +loop { + break (loop {}: !); +}: u32; +loop { + // ... + break 42; + // ... + break panic!(); +}: u32; + +// break EXPRs are not of the same type, but both coerce to `&[u8]`. +let x = [0; 32]; +let y = [0; 48]; +loop { + // ... + break &x; + // ... + break &y; +}: &[u8]; +``` + + ### Result value A loop only yields a value if broken via some form of `break ...;` statement,