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: Local `loop` bindings #2617
Conversation
Centril
added
T-lang
A-expressions
A-control-flow
A-syntax
labels
Dec 25, 2018
Centril
changed the title
Create 0000-local-loop-bindings.md
RFC: Local `loop` bindings
Dec 25, 2018
l4l
reviewed
Dec 25, 2018
text/0000-local-loop-bindings.md Outdated
Centril
reviewed
Dec 25, 2018
text/0000-local-loop-bindings.md Outdated
text/0000-local-loop-bindings.md Outdated
text/0000-local-loop-bindings.md Outdated
text/0000-local-loop-bindings.md Outdated
|
|
||
| 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. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
scottmcm
Jan 8, 2019
Member
FWIW, mutable local variables are regularly converted to registers by LLVM. I wouldn't expect any optimization differences here, especially since the new syntax would disappear by the time we get to MIR anyway.
text/0000-local-loop-bindings.md Outdated
| } | ||
| } | ||
| } | ||
| ``` |
This comment has been minimized.
This comment has been minimized.
Centril
Dec 25, 2018
Contributor
An alternative desugaring covering refutable and irrefutable patterns would be:
loop PAT = EXPR {
BODY
}
==>
{
let mut tmp = EXPR;
loop {
match tmp {
PAT => tmp = { BODY },
_ => break, // If the pattern is irrefutable this will never happen.
}
}
}In particular this lets us write:
loop (mut x, false) = (5, false) {
x += x - 3;
println!("{}", x);
(x, x % 5 == 0)
}Not sure whether this is a good thing, but it seems possible to extend your construct to refutable patterns.
This comment has been minimized.
This comment has been minimized.
porky11
Dec 26, 2018
Author
Adding this to section "Future possibilities" or maybe "Alternatives" seems better to me.
I'm not sure, if it's a good thing either.
In Scopes it's not, but it also doesn't have inbuilt variant types, so this won't help.
| 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 |
This comment has been minimized.
This comment has been minimized.
Centril
Dec 25, 2018
•
Contributor
Maybe this is bikeshed, but personally, I would find it good to reuse let pat = expr so that you write:
loop let (mut x, done) = (5, false) {
if done { break; }
x += x - 3;
println!("{}", x);
(x, x % 5 == 0)
}This seems more consistent with the rest of Rust and it also immediately tells the user that what follows after let is a pattern.
This comment has been minimized.
This comment has been minimized.
porky11
Dec 26, 2018
Author
I thought, while let and if let are extended versions of if and while, which take bools.
Here we don't have arguments anyway, so let is not necessary.
But when having a let, would it be better to accept refutable patterns, too, for consistency with while let and if let?
Also when thinking about adding more possible keywords after while and if not having a let only after loop, but also having the same new keywords (maybe while const, if const and loop const) will be inconsistent.
So yeah, loop let is the syntax, I'd prefer, too.
This comment has been minimized.
This comment has been minimized.
Centril
Dec 26, 2018
Contributor
I thought,
while letandif letare extended versions ofifandwhile, which take bools.
Here we don't have arguments anyway, soletis not necessary.
I don't think it's for necessity; it's clearly not necessary syntactically; but it helps with clarity of highlighting that a pattern comes next. Just by seeing let p = q I know that p is a pattern and q is an expression. This reduces the overall complexity of adding loop let. With loop p = q { .. } that isn't as clear and complexity costs increase.
But when having a
let, would it be better to accept refutable patterns, too, for consistency withwhile letandif let?
Sure
Also when thinking about adding more possible keywords after
whileandifnot having aletonly afterloop, but also having the same new keywords (maybewhile const,if constandloop const) will be inconsistent.
Not sure what while const would be...
So yeah,
loop letis the syntax, I'd prefer, too.
| 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 comment has been minimized.
This comment has been minimized.
Centril
Dec 25, 2018
Contributor
Could you elaborate on this with a code example of what this would look like?
This comment has been minimized.
This comment has been minimized.
porky11
Dec 26, 2018
Author
It's really not straightforward to add a syntax for this case.
I had something like 'label: let ... { body } in mind, but this is weird.
Maybe 'label x = y: { body } will work.
Loop could then look like this: 'label x = y: loop { body }
The only difference is, if the block returns or repeats by default. break and continue would do the same.
But requiring a label for this feature seems stupid anyway, so it's not a generalization at least. You probably don't want blocks to repeat anyway. Maybe I should delete this again.
Centril
and others
added some commits
Dec 25, 2018
This comment has been minimized.
This comment has been minimized.
alexreg
commented
Dec 26, 2018
|
Thanks for the proposal, @porky11. Interesting idea! I definitely think this has some use cases. A few questions though:
|
This comment has been minimized.
This comment has been minimized.
Not yet, but I already have it in mind.
I just replied to it. Also a good point, that |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Dec 26, 2018
|
Just fyi, you can build this with standard iterator adaptors now:
In practice you'd be doing something succinct like
of I'm nervous about yet more complex loop structures. We'd gain more from conventions for error propagation from inside closures, of which |
This comment has been minimized.
This comment has been minimized.
alexreg
commented
Dec 26, 2018
Yeah... I've thought over the alternatives here a bit, and I can't come up with one I'm totally satisfied with. I kind of feel this is a consequence of having two syntaxes ( |
This comment has been minimized.
This comment has been minimized.
These implementations are more confusing instead of expressing what's really going on.
This is pretty nice, but won't work for most cases. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Dec 26, 2018
•
|
I think familiarity with
If you accept to write for only a passing familiarity with Rust, then you can write more concisely by adopting the functional programming conventions like In effect, the syntax proposed here creates an imperative "fold" operation, which improves conciseness, but only by being an unfamiliar syntax. Really, you should follow functional programming conventions if you want to do this both concisely and readably, meaning We have no exceptions in Rust so our error handling requires tweaks like We might discuss if some restricted form of closure could admit a limited form of exception. Imho, we should instead focus on making |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Dec 27, 2018
•
No, this is not true. A Otherwise, I'm sorry to say but I dislike this proposal quite much.
Furthermore, considering the factorial example:
This is not any easier than: fn factorial(x: i32) -> i32 {
let mut result = 1;
for count in 1..=x {
result *= count;
}
result
}In fact, the proposed version is longer and less clear. In a bigger, more complicated loop, I wouldn't like to have to trace all the occurrences of the changing bindings. I feel that writing code in this style would cause more harm than good. A couple more points:
A simple I also fail to see how the proposal would be beneficial for someone who is not (yet) well-acquainted enough with Rust to recognize the ubiquitous
|
This comment has been minimized.
This comment has been minimized.
|
This seems a lot to me like trying to emulate C-style for loops. I'm not sure I like it. |
This comment has been minimized.
This comment has been minimized.
|
Alternative random idea: what if fn factorial(x: i32) -> i32 {
while let (result, count) = (1, x) {
if count == 1 {
break result;
}
continue (result * count, count - 1)
}
}Probably you should disallow implicitly or explicitly continuing with no value in this case, to avoid accidentally reevaluating the original |
This comment has been minimized.
This comment has been minimized.
|
That would be a bit strange... I think break and continue should similar things to other languages. |
Centril
assigned
cramertj
Jan 3, 2019
This comment has been minimized.
This comment has been minimized.
|
@alercah let mut i = 0;
while let Some(x) = list.pop() {
i += 1;
println!("iteration {}", i);
x.handle();
if let Some(child) = x.get_child() {
continue Some(child)
}
}
assert!(i == list.length())I don't think, it will be intuitive, why in some cases, the assertion would fail. |
This comment has been minimized.
This comment has been minimized.
|
@rfcbot fcp close I think there's a pretty high bar for adding new, unfamiliar syntax extensions to everyday constructs like |
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Jan 8, 2019
•
|
Team member @cramertj has proposed to close this. The next step is review by the rest of the tagged teams:
No concerns currently listed. Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
rfcbot
added
proposed-final-comment-period
disposition-close
labels
Jan 8, 2019
This comment has been minimized.
This comment has been minimized.
|
@rfcbot reviewed |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Jan 9, 2019
|
I think the practical take away: There are many closures for which |
This comment has been minimized.
This comment has been minimized.
kestred
commented
Jan 9, 2019
•
|
I feel that this RFC as written introduces new syntax functionality in an unexpected and harder to teach way for newcomers to the language (and probably also for regular rust users who don't also actively follow RFCs), compared to the following currently supported transformation:
IMO, this problem stems from the fact that For that reason, if this RFC were to be re-opened eventually, I'd heavily lean towards the Introducing In that case introducing All of that to say that I agree with detractors of the RFC that the complexity introduced here doesn't justify the benefits currently described in the motivation, and I support the close disposition. Edited: As an aside, I don't see |
rfcbot
added
final-comment-period
and removed
proposed-final-comment-period
labels
Jan 9, 2019
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Jan 9, 2019
|
|
This comment has been minimized.
This comment has been minimized.
graydon
commented
Jan 12, 2019
|
Opposed. Lisp-ism that (as much as I love Lisp) would just confuse most Rust readers. |
rfcbot
added
finished-final-comment-period
and removed
final-comment-period
labels
Jan 19, 2019
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Jan 19, 2019
|
The final comment period, with a disposition to close, as per the review above, is now complete. By the power vested in me by Rust, I hereby close this RFC. |
porky11 commentedDec 25, 2018
This RFC is about adding local loop bindings.
Rendered
Here a simple example for the new syntax: