From 332a1edf58bcb2315fa1535c9bff69b90520e325 Mon Sep 17 00:00:00 2001 From: P1start Date: Sun, 24 Aug 2014 19:20:59 +1200 Subject: [PATCH 1/8] Loops returning values --- active/0000-loops-returning-values.md | 148 ++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 active/0000-loops-returning-values.md diff --git a/active/0000-loops-returning-values.md b/active/0000-loops-returning-values.md new file mode 100644 index 00000000000..c670c373dfe --- /dev/null +++ b/active/0000-loops-returning-values.md @@ -0,0 +1,148 @@ +Summary +======= + +Extend `for`, `loop`, and `while` loops to allow them to return values other +than `()`: + +- add an optional `else` clause that is evaluated if the loop ended without + using `break`; +- add an optional expression parameter to `break` expressions to break out of + a loop with a value. + +Motivation +========== + +Quite often a variable is used with loops to keep track of something. For +example, the following code could be used to find a value in a list: + +```rust +fn find(list: Vec, val: int) -> Option { + let mut index = None; + for (i, v) in list.iter().enumerate() { + if *v == val { + index = Some(i); + break; + } + } + index +} +``` + +However, this code relies on mutable state when it really shouldn’t be +necessary—what this code is actually doing is simply setting a variable to a +value *one time*, with a default value if the assignment statement was never +reached. + +Loops also don’t fit in with Rust’s idea of ‘everything is an expression’: while +loops *are* expressions, their value is completely useless. Making loops return +meaningful values would fit in better with Rust’s idea of being able to use +everything as an expression. Iterator adaptors can often be used to similar +effect, but they aren’t always as flexible as `for`, `loop`, and `while` loops: +they cannot `return` from the enclosing function and do not provide any +guarantees to the compiler about how they run the given closure, preventing the +compiler from knowing that a variable will only be initialised once, for +example. + +Detailed design +=============== + +Extend `for` and `while` (and `while let`) to have an optional extra `else` +clause. This clause is evaluated if and only if the loop finished iterating +without reaching a `break` statement. This `else` clause, when reached, makes +the loop expression evaluate to the value within. When omitted, it is equivalent +to an empty `else` clause returning `()`. The syntax for this additional clause +comes from Python, where it is used for the same thing. + +Add an optional expression parameter to `break` statements following the label +(if any). A `break` expression with a value breaks out of the loop and makes the +loop evaluate to the given expression. The type of the `break` statement’s +expression must be the same as that of the `else` clause and that of any other +`break` expression. Because `loop` loops have no `else` clause, this restriction +does not apply to them. + +An advantage of having this new kind of construct is that the compiler can know +that either the main loop body or the `else` clause will *always* be run at +least once. This means that the following code would be valid: + +```rust +let haystack = vec![1i, 2, 3, 4]; +let needle = 2; + +let x; +frobnicate(for (i, v) in haystack.iter().enumerate() { + if v >= needle { + x = i; + break v; + } +} else { + x = -1; + 0 +}); +``` + +because the compiler knows that `x` will be assigned to exactly once. + +Example +------- + +The following statement: + +```rust +let x = while w { + code; + if cond { break brk; } +} else { + els +}; +``` + +would iterate like a normal `while` loop does today. However, if `cond` +evaluates to `true`, then the entire loop would evaluate to `brk`, setting `x` +to `brk`. If `cond` never evaluated to `true` in its entire cycle (i.e., the +`break` statement was never reached), then the loop would evaluate to `els`, +thus setting `x` to `els`. + +In other words, it would be roughly equivalent to something like this: + +```rust +let x = { + let mut _res = None; + while w { + code; + if cond { _res = Some(brk); break; } + } + match _res { + None => { + els + } + Some(res) => res + } +}; +``` + +Drawbacks +========= + +* Complexity. This adds some complexity which perhaps could be considered + unnecessary. However, this does have precedent in languages like Python, and + so presumably does have some demand. +* The syntax is not very obvious: `else` perhaps suggests what would run if the + loop didn’t iterate over anything. + +Alternatives +============ + +* Do nothing. Instead, rely on using mutable variables to keep track of + something within a loop. +* Use `nobreak` or something instead of `else`. This makes things a lot clearer, + but has the downside of introducing a new keyword, making this a + backward-incompatible change. +* Make iterators yield `Result` instead of `Option` when calling + `next`, and adjust `for` loops to evaluate to the expression parameter of any + `break` encountered or the `Err` part of `next`’s return value. This could be + done in addition to this proposal. + +Unresolved questions +==================== + +None. From 8edc8b569a7b97359f48c2b759c3768579e03edb Mon Sep 17 00:00:00 2001 From: P1start Date: Sun, 5 Oct 2014 17:43:24 +1300 Subject: [PATCH 2/8] Tweak the explanation and alternatives --- active/0000-loops-returning-values.md | 34 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/active/0000-loops-returning-values.md b/active/0000-loops-returning-values.md index c670c373dfe..4edda7f67d2 100644 --- a/active/0000-loops-returning-values.md +++ b/active/0000-loops-returning-values.md @@ -21,7 +21,7 @@ fn find(list: Vec, val: int) -> Option { for (i, v) in list.iter().enumerate() { if *v == val { index = Some(i); - break; + break } } index @@ -72,7 +72,7 @@ let x; frobnicate(for (i, v) in haystack.iter().enumerate() { if v >= needle { x = i; - break v; + break v } } else { x = -1; @@ -90,7 +90,7 @@ The following statement: ```rust let x = while w { code; - if cond { break brk; } + if cond { break brk } } else { els }; @@ -106,20 +106,24 @@ In other words, it would be roughly equivalent to something like this: ```rust let x = { - let mut _res = None; - while w { - code; - if cond { _res = Some(brk); break; } - } - match _res { - None => { - els + let _res; + loop { + if w { + code; + if cond { _res = brk; break } + } else { + _res = els; + break } - Some(res) => res } + _res }; ``` +This ‘translation’ also helps explain the use of the `else` keyword here: the +`else` clause is run if the condition (here `w`) failed, much like how the +`else` clause is run in an `if` expression if the condition failed. + Drawbacks ========= @@ -133,7 +137,11 @@ Alternatives ============ * Do nothing. Instead, rely on using mutable variables to keep track of - something within a loop. + something within a loop, or use the methods provided by the `Iterator` trait. + However, the same argument could be used to propose that `for` loops should be + removed altogether in favour of `map`, which presumably would not be a popular + change, if only because `break` and returning from the outer function would be + disallowed. * Use `nobreak` or something instead of `else`. This makes things a lot clearer, but has the downside of introducing a new keyword, making this a backward-incompatible change. From 4d6fc060bbc438b58e8d9c4a9038ff6b25600c26 Mon Sep 17 00:00:00 2001 From: P1start Date: Tue, 21 Oct 2014 15:27:26 +1300 Subject: [PATCH 3/8] Move to new directory layout --- {active => text}/0000-loops-returning-values.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {active => text}/0000-loops-returning-values.md (100%) diff --git a/active/0000-loops-returning-values.md b/text/0000-loops-returning-values.md similarity index 100% rename from active/0000-loops-returning-values.md rename to text/0000-loops-returning-values.md From 6d286a7d6c58419c1389df6d48b1097ff95e9c56 Mon Sep 17 00:00:00 2001 From: P1start Date: Thu, 23 Oct 2014 20:25:36 +1300 Subject: [PATCH 4/8] Add more examples; clarify that `loop`s need no `else` block; consider `!break` --- text/0000-loops-returning-values.md | 98 +++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 12 deletions(-) diff --git a/text/0000-loops-returning-values.md b/text/0000-loops-returning-values.md index 4edda7f67d2..fcb2b912bab 100644 --- a/text/0000-loops-returning-values.md +++ b/text/0000-loops-returning-values.md @@ -46,19 +46,20 @@ example. Detailed design =============== -Extend `for` and `while` (and `while let`) to have an optional extra `else` -clause. This clause is evaluated if and only if the loop finished iterating -without reaching a `break` statement. This `else` clause, when reached, makes -the loop expression evaluate to the value within. When omitted, it is equivalent -to an empty `else` clause returning `()`. The syntax for this additional clause -comes from Python, where it is used for the same thing. +Extend `for`, `while`, and `while let` (but not `loop`) to have an optional +extra `else` clause. This clause is evaluated if and only if the loop finished +iterating without reaching a `break` statement. This `else` clause, when +reached, makes the loop expression evaluate to the value within. When omitted, +it is equivalent to an empty `else` clause returning `()`. The syntax for this +additional clause comes from Python, where it is used for the same thing. Add an optional expression parameter to `break` statements following the label (if any). A `break` expression with a value breaks out of the loop and makes the -loop evaluate to the given expression. The type of the `break` statement’s +loop evaluate to the given expression. `break` expressions have the same +precedence as `return` expressions. The type of the `break` statement’s expression must be the same as that of the `else` clause and that of any other -`break` expression. Because `loop` loops have no `else` clause, this restriction -does not apply to them. +`break` expression. Because `loop` loops have no `else` clause, their `break`s +only need to match types with each other. An advantage of having this new kind of construct is that the compiler can know that either the main loop body or the `else` clause will *always* be run at @@ -82,8 +83,8 @@ frobnicate(for (i, v) in haystack.iter().enumerate() { because the compiler knows that `x` will be assigned to exactly once. -Example -------- +Examples +-------- The following statement: @@ -124,6 +125,76 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th `else` clause is run if the condition (here `w`) failed, much like how the `else` clause is run in an `if` expression if the condition failed. +### Valid samples + +- ```rust + let x: int = while cond { + break 1 + }; + ``` + + Here the `else` clause is allowed to be omitted (inferred to be an empty block + of type `()`) because the type of the body block is `!`, which unifies with + `()`. + +- ```rust + let x: int = while cond { + foo(); + if let Some(foo) = bar() { break foo } + } else { + 0 + }; + ``` + + The types of the `else` and `break` clauses are the same, and they also match + the type of the variable the loop is assigned to, so this typechecks. + +- ```rust + let z: int; + let x: int = 'a: while cond { + let y: f64 = while foo() { + if bar() { z = 1; break 'a 1 } + if baz() { break 1.618 } + } else { + 6.283 + }; + if y > 5 { z = 2; break y as int } + } else { + z = 3; + 0 + }; + ``` + + This example demonstrates labelled `break`s/`continue`s: the type of the + expression passed to the `break` has to be the same as the type of the loop + with the corresponding label. Additionally, `z` is always going to be assigned + to exactly once: every assignment inside the outer `while` loop’s main body is + followed by a `break` for the outer loop, and it is assigned to exactly once + in the `else` clause. + +### Invalid samples + +- ```rust + let x = while cond { + if foo() { break 1i } + }; + ``` + + This example would not typecheck, because the type of the `break`’s expression + (`int`) does not match the type of the (omitted) `else` block (`()`). + +- ```rust + let x: int; + while cond { + if foo() { x = 1 } + } else { + x = 2 + } + ``` + + In this example, `x` could be assigned to more than once, so this would be + invalid. + Drawbacks ========= @@ -144,7 +215,10 @@ Alternatives disallowed. * Use `nobreak` or something instead of `else`. This makes things a lot clearer, but has the downside of introducing a new keyword, making this a - backward-incompatible change. + backward-incompatible change. Alternatively, `!break` could be used, avoiding + the introduction of a new keyword. Unfortunately, this looks quite cryptic, + and could be tricky to parse (although it is not ambiguous), especially given + that `!break` is currently a valid expression. * Make iterators yield `Result` instead of `Option` when calling `next`, and adjust `for` loops to evaluate to the expression parameter of any `break` encountered or the `Err` part of `next`’s return value. This could be From 1b7a5d00ef9fbaf03aab85e65849b4c3f982ba6d Mon Sep 17 00:00:00 2001 From: P1start Date: Thu, 23 Oct 2014 20:29:48 +1300 Subject: [PATCH 5/8] Attempt to fix formatting issues --- text/0000-loops-returning-values.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-loops-returning-values.md b/text/0000-loops-returning-values.md index fcb2b912bab..f629afb8045 100644 --- a/text/0000-loops-returning-values.md +++ b/text/0000-loops-returning-values.md @@ -127,6 +127,7 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th ### Valid samples + - ```rust let x: int = while cond { break 1 @@ -137,6 +138,7 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th of type `()`) because the type of the body block is `!`, which unifies with `()`. + - ```rust let x: int = while cond { foo(); @@ -149,6 +151,7 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th The types of the `else` and `break` clauses are the same, and they also match the type of the variable the loop is assigned to, so this typechecks. + - ```rust let z: int; let x: int = 'a: while cond { @@ -172,6 +175,7 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th followed by a `break` for the outer loop, and it is assigned to exactly once in the `else` clause. + ### Invalid samples - ```rust @@ -183,6 +187,7 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th This example would not typecheck, because the type of the `break`’s expression (`int`) does not match the type of the (omitted) `else` block (`()`). + - ```rust let x: int; while cond { @@ -195,6 +200,7 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th In this example, `x` could be assigned to more than once, so this would be invalid. + Drawbacks ========= From f75997d66ce54a65cf28b0453f023c4ece7461c9 Mon Sep 17 00:00:00 2001 From: P1start Date: Thu, 23 Oct 2014 20:36:03 +1300 Subject: [PATCH 6/8] =?UTF-8?q?Let=E2=80=99s=20try=20this?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- text/0000-loops-returning-values.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/text/0000-loops-returning-values.md b/text/0000-loops-returning-values.md index f629afb8045..965531dc53c 100644 --- a/text/0000-loops-returning-values.md +++ b/text/0000-loops-returning-values.md @@ -127,7 +127,6 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th ### Valid samples - - ```rust let x: int = while cond { break 1 @@ -137,8 +136,6 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th Here the `else` clause is allowed to be omitted (inferred to be an empty block of type `()`) because the type of the body block is `!`, which unifies with `()`. - - - ```rust let x: int = while cond { foo(); @@ -150,8 +147,6 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th The types of the `else` and `break` clauses are the same, and they also match the type of the variable the loop is assigned to, so this typechecks. - - - ```rust let z: int; let x: int = 'a: while cond { @@ -175,7 +170,6 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th followed by a `break` for the outer loop, and it is assigned to exactly once in the `else` clause. - ### Invalid samples - ```rust @@ -186,8 +180,6 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th This example would not typecheck, because the type of the `break`’s expression (`int`) does not match the type of the (omitted) `else` block (`()`). - - - ```rust let x: int; while cond { @@ -200,7 +192,6 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th In this example, `x` could be assigned to more than once, so this would be invalid. - Drawbacks ========= From b55ec51ce0a4906272662ed9b9ea5fcce2491c17 Mon Sep 17 00:00:00 2001 From: P1start Date: Thu, 23 Oct 2014 22:16:33 +1300 Subject: [PATCH 7/8] Hopefully work around formatting issues --- text/0000-loops-returning-values.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/text/0000-loops-returning-values.md b/text/0000-loops-returning-values.md index 965531dc53c..bc7cac744fe 100644 --- a/text/0000-loops-returning-values.md +++ b/text/0000-loops-returning-values.md @@ -127,7 +127,9 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th ### Valid samples -- ```rust +- `while` block with type `!`: + + ```rust let x: int = while cond { break 1 }; @@ -136,7 +138,10 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th Here the `else` clause is allowed to be omitted (inferred to be an empty block of type `()`) because the type of the body block is `!`, which unifies with `()`. -- ```rust + +- `while`…`else`: + + ```rust let x: int = while cond { foo(); if let Some(foo) = bar() { break foo } @@ -147,7 +152,10 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th The types of the `else` and `break` clauses are the same, and they also match the type of the variable the loop is assigned to, so this typechecks. -- ```rust + +- Complicated control flow: + + ```rust let z: int; let x: int = 'a: while cond { let y: f64 = while foo() { @@ -172,7 +180,9 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th ### Invalid samples -- ```rust +- `while` without `else` but with `break` expression: + + ```rust let x = while cond { if foo() { break 1i } }; @@ -180,7 +190,10 @@ This ‘translation’ also helps explain the use of the `else` keyword here: th This example would not typecheck, because the type of the `break`’s expression (`int`) does not match the type of the (omitted) `else` block (`()`). -- ```rust +  +- Multiple assignments to an immutable variable: + + ```rust let x: int; while cond { if foo() { x = 1 } From 19d43eb8a55c96402cfaa70ab74b35b26fb1d429 Mon Sep 17 00:00:00 2001 From: P1start Date: Wed, 29 Oct 2014 15:17:01 +1300 Subject: [PATCH 8/8] Add the required RFC header --- text/0000-loops-returning-values.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-loops-returning-values.md b/text/0000-loops-returning-values.md index bc7cac744fe..6ea1b116ac5 100644 --- a/text/0000-loops-returning-values.md +++ b/text/0000-loops-returning-values.md @@ -1,3 +1,7 @@ +- Start Date: 2014-10-04 +- RFC PR #: (leave this empty) +- Rust Issue #: (leave this empty) + Summary =======