From 7fea233dc787c0263c4ac7bcdaa488cab4a3b403 Mon Sep 17 00:00:00 2001 From: Fabio Krapohl Date: Tue, 25 Dec 2018 17:25:53 +0000 Subject: [PATCH 1/9] Create 0000-local-loop-bindings.md This RFC is about adding local loop bindings. Here a simple example for the new syntax: ```rust fn factorial(x: i32) -> i32 { loop (result, count) = (1, x) { if count == 1 { break result; } (result * count, count - 1) } } ``` --- text/0000-local-loop-bindings.md | 218 +++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 text/0000-local-loop-bindings.md diff --git a/text/0000-local-loop-bindings.md b/text/0000-local-loop-bindings.md new file mode 100644 index 00000000000..cd82c37a3cb --- /dev/null +++ b/text/0000-local-loop-bindings.md @@ -0,0 +1,218 @@ +- Feature Name: local_loop_bindings +- Start Date: 2018-12-25 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +This will allow an extended syntax for `loop` to accept local variables, which change for each iteration to simplify more complicated loop constructs and avoid mutable state. + + +# Motivation +[motivation]: #motivation + +The new syntax is inspired by `loop` in the upcoming release of the [scopes programming language](scopes.rocks). +This way it's possible use different values for each iteration without the need of mutable variables defined outside of the loop. + +The variables will be defined after the loop keyword, so they will only be accessible in the scope of the loop, not afterwards. They will not be mutable by default, so it can be ensured, that the variables only change once per iteration. + +Especially since loops can return values, it's not necessary at all to mutate state inside a loop in some cases. + +This is a more functional programming style, which may also allow more optimizations like storing the loop arguments in registers instead of allocating storage for mutable variables. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The extended syntax for `loop` will just work like a `while let`. "while let" will be replaced by "loop". Unlike `while let`, the pattern is not allowed to be refutable, so enum variants are not allowed on the lefthand side of "=". +The introduced bindings will be accessible in this loop. + +The new syntax will look like this: + +```rust +loop binding = value { + /* body */ +} +``` + +The return value of the loop body will implicitely be passed to the next iteration of the loop, so it needs to be the same type as the initial value. + +An example of a simple iteration, which iterates the loop ten times and prints the iteration number would look like this: + +```rust +loop i = 1 { + if i <= 10 { + println!("iteration {}", i); + i + 1 + } else { + break; + } +} +``` + +`continue` will accept an argument in this loop, which will be passed to the next iteration. Using continue, this could look like this: + +```rust +loop i = 1 { + if i <= 10 { + println!("iteration {}", i); + continue i + 1; + } + break; +} +``` + +Since the end of the loop is never reached, the return value is not required to be the type of the binding, here. + +A loop without bindings (`loop { /* body */ }`) will be the same as this: + +```rust +loop () = () { + /* body */ +} +``` + +This will not be a breaking change, since it's not allowed to have values other than `()` from a loop. + +A simple example from the book looks like this: + +```rust +let mut x = 5; +let mut done = false; + +while !done { + x += x - 3; + + println!("{}", x); + + if x % 5 == 0 { + done = true; + } +} +``` + +Using the new syntax, this could be rewritten as this: + +```rust +loop (mut x, done) = (5, false) { + if done { + break; + } + x += x - 3; + + println!("{}", x); + + if x % 5 == 0 { + (x, true) + } else { + (x, false) + } +} +``` + +This is, how you would define factorial using a loop now: + +```rust +fn factorial(x: i32) -> i32 { + loop (result, count) = (1, x) { + if count == 1 { + break result; + } + (result * count, count - 1) + } +} +``` + +With explicit `continue`, it can look like this: + +```rust +fn factorial(x: i32) -> i32 { + loop (result, count) = (1, x) { + if count == 1 { + break result; + } else { + continue (result * count, count - 1); + } + } +} +``` + +Using `break` here allows copying code without having to modify it, when not using a specific function. + +Labels will also work. When using `continue` with a label, the arguments to continue must match the loop binding signature connected to the label, in case the label is connected with a loop. + + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The syntax extension should not cause any issues with backwards compatability. + +It's just an extended syntax for `loop` in a place, where currently nothing is allowed yet. + +The expansion of the new syntax will be shown for an example. + +New syntax: + +```rust +loop (a, mut b) = (x, y) { + /* body */ +} +``` + +Current syntax: + +```rust +{ // ensure global bindings to be inaccessible after the loop + let mut binding = (x, y); + loop { + let (a, mut b) = binding; + binding = { + /* body */ + } + } +} +``` + +This expansion should cover the common case. + +A `continue value` in the body would expand to `binding = value; continue; + +Internally there may be more efficient ways to implement this. + + +# Drawbacks +[drawbacks]: #drawbacks + +This adds more options to the language, which also makes the language more complicated, but it should be pretty intuitive, how it works. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +It would be possible to extend `while let` instead, so it supports both refutable and irrefutable value and add additionally add support for `continue`, but in one case the expression generating the value is called for each iteration and in the other case only in the beginning, so this is probably not an option. + +To avoid confusion, it would be possible to require a `continue` branch to repeat. Any branch reaching the end without `continue` would fail. + +It would also be possible to just have labeled blocks with bindings, similar to "named let", as known from Scheme. In this case, reaching the end of the block will just leave the loop and go on afterwards. +This could be a more general version, which is not connected to loops, but can be used for everything, which can have labels. + + +# Prior art +[prior-art]: #prior-art + +Without the feature of loops being able to return values, this feature is less useful. + +Labeled blocks, which are currently unstable, may also be useful for some alternative to this. + + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +There are some other design decisions, mentioned as alternatives, which could be taken into account instead. +But I'm pretty sure, the proposal itself is more useful and straightforward than the alternatives. +There are no unresolved questions yet. + +# Future possibilities +[future-possibilities]: #future-possibilities + +If named blocks are stabilized, they could additionally allow local bindings, like a "named let". + From 22567d988792066b65979c37b85e79b6f4573608 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 26 Dec 2018 00:31:49 +0100 Subject: [PATCH 2/9] Suggested change for summary Co-Authored-By: porky11 --- text/0000-local-loop-bindings.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/0000-local-loop-bindings.md b/text/0000-local-loop-bindings.md index cd82c37a3cb..425b0cc5be9 100644 --- a/text/0000-local-loop-bindings.md +++ b/text/0000-local-loop-bindings.md @@ -6,7 +6,8 @@ # Summary [summary]: #summary -This will allow an extended syntax for `loop` to accept local variables, which change for each iteration to simplify more complicated loop constructs and avoid mutable state. +To simplify complicated loop constructs and avoid mutable state, +allow an extended syntax for `loop` to accept local variables that may change once per iteration. # Motivation From 174bc092e5c066a7ecf025ab17345e1e3cd669ef Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 26 Dec 2018 00:33:36 +0100 Subject: [PATCH 3/9] Update text/0000-local-loop-bindings.md Co-Authored-By: porky11 --- text/0000-local-loop-bindings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-local-loop-bindings.md b/text/0000-local-loop-bindings.md index 425b0cc5be9..7eac15db605 100644 --- a/text/0000-local-loop-bindings.md +++ b/text/0000-local-loop-bindings.md @@ -14,7 +14,7 @@ allow an extended syntax for `loop` to accept local variables that may change on [motivation]: #motivation The new syntax is inspired by `loop` in the upcoming release of the [scopes programming language](scopes.rocks). -This way it's possible use different values for each iteration without the need of mutable variables defined outside of the loop. +The chief motivation is to enable using different values for each iteration without the need of mutable bindings defined outside of the loop. The variables will be defined after the loop keyword, so they will only be accessible in the scope of the loop, not afterwards. They will not be mutable by default, so it can be ensured, that the variables only change once per iteration. From a84219609fc7fdfe523680b336a6482b373b4a64 Mon Sep 17 00:00:00 2001 From: Fabio Krapohl Date: Tue, 25 Dec 2018 23:40:29 +0000 Subject: [PATCH 4/9] Add example to summary --- text/0000-local-loop-bindings.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/text/0000-local-loop-bindings.md b/text/0000-local-loop-bindings.md index 7eac15db605..79f98d52c2d 100644 --- a/text/0000-local-loop-bindings.md +++ b/text/0000-local-loop-bindings.md @@ -9,6 +9,19 @@ To simplify complicated loop constructs and avoid mutable state, allow an extended syntax for `loop` to accept local variables that may change once per iteration. +To get an idea of what this is about, here you already can see a simple example for factorial using the new syntax: + +```rust +fn factorial(x: i32) -> i32 { + loop (result, count) = (1, x) { + if count == 1 { + break result; + } else { + continue (result * count, count - 1); + } + } +} +``` # Motivation [motivation]: #motivation From 23082679c627656b26cbb676c64380c2a479433e Mon Sep 17 00:00:00 2001 From: Fabio Krapohl Date: Tue, 25 Dec 2018 23:46:57 +0000 Subject: [PATCH 5/9] Scopes example update --- text/0000-local-loop-bindings.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/text/0000-local-loop-bindings.md b/text/0000-local-loop-bindings.md index 79f98d52c2d..e58f33e34f3 100644 --- a/text/0000-local-loop-bindings.md +++ b/text/0000-local-loop-bindings.md @@ -26,7 +26,6 @@ fn factorial(x: i32) -> i32 { # Motivation [motivation]: #motivation -The new syntax is inspired by `loop` in the upcoming release of the [scopes programming language](scopes.rocks). The chief motivation is to enable using different values for each iteration without the need of mutable bindings defined outside of the loop. The variables will be defined after the loop keyword, so they will only be accessible in the scope of the loop, not afterwards. They will not be mutable by default, so it can be ensured, that the variables only change once per iteration. @@ -189,7 +188,7 @@ Current syntax: This expansion should cover the common case. -A `continue value` in the body would expand to `binding = value; continue; +A `continue value` in the body would expand to `binding = value; continue;` Internally there may be more efficient ways to implement this. @@ -213,11 +212,32 @@ This could be a more general version, which is not connected to loops, but can b # Prior art [prior-art]: #prior-art +## Scopes + +The main inspiration was the `loop` construct in the upcoming release of the [scopes programming language](scopes.rocks) ([this commit](https://bitbucket.org/duangle/scopes/commits/6a44e062e6a4a7813146a850c8982c0f902eefba)). +Documentation is still raw and things may change, but the current version of loop matches best with rust. + +The same example of factorial should look like this in the next scopes release: + +```scopes +fn factorial (x) + loop (result count = 1 x) + if (count == 1) + break result + else + continue + result * count + count - 1 +``` + +## Rust specific + Without the feature of loops being able to return values, this feature is less useful. Labeled blocks, which are currently unstable, may also be useful for some alternative to this. + # Unresolved questions [unresolved-questions]: #unresolved-questions From 9f9ff598df901d266ac106ed2c49c20959a87d3b Mon Sep 17 00:00:00 2001 From: Fabio Krapohl Date: Tue, 25 Dec 2018 23:48:18 +0000 Subject: [PATCH 6/9] commit suggestion variables->bindings --- text/0000-local-loop-bindings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-local-loop-bindings.md b/text/0000-local-loop-bindings.md index e58f33e34f3..d4e0fc8e4a2 100644 --- a/text/0000-local-loop-bindings.md +++ b/text/0000-local-loop-bindings.md @@ -28,7 +28,7 @@ fn factorial(x: i32) -> i32 { The chief motivation is to enable using different values for each iteration without the need of mutable bindings defined outside of the loop. -The variables will be defined after the loop keyword, so they will only be accessible in the scope of the loop, not afterwards. They will not be mutable by default, so it can be ensured, that the variables only change once per iteration. +The bindings will be defined after the `loop` keyword, making them only accessible in the scope of the loop, not afterwards. As usual, they will not be mutable by default, which helps to ensure, that the variables change at most once per iteration. Especially since loops can return values, it's not necessary at all to mutate state inside a loop in some cases. From a20af5f9511e7505e87e2a320d3c90828158cc3a Mon Sep 17 00:00:00 2001 From: Fabio Krapohl Date: Wed, 26 Dec 2018 00:09:08 +0000 Subject: [PATCH 7/9] Update to refutable values --- text/0000-local-loop-bindings.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/text/0000-local-loop-bindings.md b/text/0000-local-loop-bindings.md index d4e0fc8e4a2..5519f16a8aa 100644 --- a/text/0000-local-loop-bindings.md +++ b/text/0000-local-loop-bindings.md @@ -37,10 +37,7 @@ This is a more functional programming style, which may also allow more optimizat # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -The extended syntax for `loop` will just work like a `while let`. "while let" will be replaced by "loop". Unlike `while let`, the pattern is not allowed to be refutable, so enum variants are not allowed on the lefthand side of "=". -The introduced bindings will be accessible in this loop. - -The new syntax will look like this: +The extended syntax for `loop` looks like this: ```rust loop binding = value { @@ -48,6 +45,8 @@ loop binding = value { } ``` +Just like when using `let` and unlike `if let`/`while let`, `binding` has to be be any irrefutable pattern, which means, that it will match for every value of the type. + The return value of the loop body will implicitely be passed to the next iteration of the loop, so it needs to be the same type as the initial value. An example of a simple iteration, which iterates the loop ten times and prints the iteration number would look like this: From 7634e2dcf0449805abe9c6bd33f3387fa0da1a74 Mon Sep 17 00:00:00 2001 From: Fabio Krapohl Date: Wed, 26 Dec 2018 00:16:55 +0000 Subject: [PATCH 8/9] Update 0000-local-loop-bindings.md --- text/0000-local-loop-bindings.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/text/0000-local-loop-bindings.md b/text/0000-local-loop-bindings.md index 5519f16a8aa..3982a2f9c9a 100644 --- a/text/0000-local-loop-bindings.md +++ b/text/0000-local-loop-bindings.md @@ -49,7 +49,7 @@ Just like when using `let` and unlike `if let`/`while let`, `binding` has to be The return value of the loop body will implicitely be passed to the next iteration of the loop, so it needs to be the same type as the initial value. -An example of a simple iteration, which iterates the loop ten times and prints the iteration number would look like this: +An example of a simple loop, which iterates the loop ten times and prints the iteration number, would look like this: ```rust loop i = 1 { @@ -111,18 +111,12 @@ loop (mut x, done) = (5, false) { break; } x += x - 3; - println!("{}", x); - - if x % 5 == 0 { - (x, true) - } else { - (x, false) - } + (x, x % 5 == 0) } ``` -This is, how you would define factorial using a loop now: +This is how you would define factorial using a loop now: ```rust fn factorial(x: i32) -> i32 { From ba934b7186a0f937b4aa946260343bba33a1baf6 Mon Sep 17 00:00:00 2001 From: Fabio Krapohl Date: Wed, 26 Dec 2018 01:25:44 +0000 Subject: [PATCH 9/9] Update 0000-local-loop-bindings.md --- text/0000-local-loop-bindings.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-local-loop-bindings.md b/text/0000-local-loop-bindings.md index 3982a2f9c9a..af0fec47bab 100644 --- a/text/0000-local-loop-bindings.md +++ b/text/0000-local-loop-bindings.md @@ -46,6 +46,7 @@ loop binding = value { ``` Just like when using `let` and unlike `if let`/`while let`, `binding` has to be be any irrefutable pattern, which means, that it will match for every value of the type. +Opposed to `let`, `loop` will require a value to supplied immediately. Else it will be unbound when entering the first iteration of the loop, but bound when entering a further iteration. The return value of the loop body will implicitely be passed to the next iteration of the loop, so it needs to be the same type as the initial value. @@ -147,6 +148,7 @@ Using `break` here allows copying code without having to modify it, when not usi Labels will also work. When using `continue` with a label, the arguments to continue must match the loop binding signature connected to the label, in case the label is connected with a loop. +Now a few examples from exisitng crates will follow soon. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -243,3 +245,4 @@ There are no unresolved questions yet. If named blocks are stabilized, they could additionally allow local bindings, like a "named let". +It's possible to add support for irrefutable patterns, too. This may just use the same syntax or a different syntax (maybe `if loop`)