-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Local loop
bindings
#2617
RFC: Local loop
bindings
#2617
Conversation
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) } } ```
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This paragraph could use some elaboration + justification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, how to explain. I won't do this yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright; take your time. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
} | ||
} | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought,
while let
andif let
are extended versions ofif
andwhile
, which take bools.
Here we don't have arguments anyway, solet
is 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 let
andif let
?
Sure 👍
Also when thinking about adding more possible keywords after
while
andif
not having alet
only afterloop
, but also having the same new keywords (maybewhile const
,if const
andloop const
) will be inconsistent.
Not sure what while const
would be...
So yeah,
loop let
is 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on this with a code example of what this would look like?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Co-Authored-By: porky11 <[email protected]>
Co-Authored-By: porky11 <[email protected]>
Thanks for the proposal, @porky11. Interesting idea! I definitely think this has some use cases. A few questions though:
|
Not yet, but I already have it in mind.
I just replied to it. Also a good point, that |
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 |
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 ( |
These implementations are more confusing instead of expressing what's really going on.
This is pretty nice, but won't work for most cases. |
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 |
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 seems a lot to me like trying to emulate C-style for loops. I'm not sure I like it. |
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 |
That would be a bit strange... I think break and continue should similar things to other languages. |
@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. |
@rfcbot fcp close I think there's a pretty high bar for adding new, unfamiliar syntax extensions to everyday constructs like |
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 reviewed |
I think the practical take away: There are many closures for which |
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 |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
Opposed. Lisp-ism that (as much as I love Lisp) would just confuse most Rust readers. |
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. |
This RFC is about adding local loop bindings.
Rendered
Here a simple example for the new syntax: