-
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
Allow for and while loop to return value (without discussion of keyword names) #1767
Comments
If the loop executes without being entered then the There is no other cases where a loop can exit without breaking -- except for I'm a little confused about this, other than the name of the
There is no need for a |
You might be right. The main reason why I thought special casing it would be nice, was so you could use the last iterator value as the return value in case nothing was found. Like the following: let x = for i in [1, 4, 3, 2] {
if i == 6 {
break 6;
}
i + 10
} noloop {
0
} However, I'm not able to quickly think of a real use case for this. And it would also mean that the last statement needs to be executed each loop execution although it is only used the last time. |
that is the precise reason that You are proposing adding an additional branch in the case that the loop is never entered ( The "last loop value" is a non-starter anyway IMO -- it would be extremely confusing to work with and non-intuative. |
Ick! I strongly prefer doing this sort of thing with I suppose #1624 seems okay because That said, if one wants this sort of feature, there are two reasonable options that avoid needing any keywords : If you like expressions everywhere, then just make If you're less dedicated to expressions, and happy to ask for more let x = 'a {
for i in ... {
...
if foo { break `a bar; }
...
}
baz
}; In both these cases, there is much less ambiguity for someone reading the code who comes from another language that may do other things. |
I have to disagree pretty heavily with anyone proposing last statement. First of all: it's not backwards compatible. Second: it can't borrow anything that the conditional also borrows. For example, we should be able to do this: let k = for i in &mut v {
if i == x { break i }
} else {
x
} This isn't possible to duplicate with the last statement, because the value in the last statement needs to be held in a temporary variable while the condition is evaluated ( let k = for i in &mut v {
if i == x { break }
i
} noloop {
x
} The big problem is that it desugars to this: let vi = v.iter_mut();
let mut k;
let mut ran = false;
while Some(i) = vi.next() {
if i == x { break }
k = i;
ran = true;
}
if !ran { k = x } Except the compiler knows k always gets initialized. |
It could easily be made backwards compatible. It would only be enabled when Your second statement is a very good reason not to do that though. On 7 Oct 2016 4:52 am, "Michael Howell" [email protected] wrote:
|
One thing to note (wrt keywords) is that since the last RFC we’ve added our first contextual keyword. Although I wouldn’t recommend adding another, it is still an option to consider. As far as behaviour goes, the @notriddle’s point is fair and has no solution (you oughtn’t work around the borrowchecker here). Remembering the result of last expression in every iteration is probably something that will not happen for this reason. This makes result-ful Probably the only viable thing to consider is running that extra block if the loop wasn’t exit through |
Yes, the only viable syntax (with all possible complications) is:
The only real discussion is whether there must be a |
|
#1624 (comment) discussed this, and you are right -- I had been of the opinion that it was necessary, I think it is just conventional. |
I believe with the current compiler architecture it would be possible to discriminate between loops whose values are used (and thus require type unification) and those whose values are not used (e.g. due to use of ;). It seems to me that this would provide the better ergonomics. |
Nominating this for @rust-lang/lang discussion, since it seems to have gotten a bit lost. Also: would this conflict with potential ways to use for/while as iterators, rather than just returning a single value? |
Personally I'm just inclined to leave this as it is. I feel it's "enough" for |
I'd prefer
|
I also feel like we shouldn't do anything here - it seems like there is no nice solution and the use case is not strong enough for something complex. |
I am strongly disinclined to include Note that fully 75% of respondents either did not know or guessed the wrong semantics (the correct answering being "the |
@nikomatsakis, that's why |
@JelteF I guess my feeling remains that the need to "produce" values from At minimum, I would want to wait until |
In my opinion, if for and while loops cannot return values, then none should. Complexity always has a cost, but non-uniform/special-case complexity is the worst |
But
The language even treats So there's nothing particularly arbitrary about stabilizing |
I don't agree at all that it's not uniform. |
@withoutboats That's the compiler writer's (or language designer's) POV, but it seems clear from previous discussion that people disagree about what the syntax should be and what it should do for |
There were multiple other possibilities for syntax discussed in the other thread fwiw, many of which are less "highly unconventional" than |
Since let r = while cond() {
if cond2() { break 42 }
foo();
} else {
52
}; Edit: Thinking about it more, I think it would make sense to add "loop break value" for if if cond1() {
foo();
cond11()
} else {
cond2()
} {
bar();
} else {
baz();
} Or: if {
foo();
cond()
} {
bar();
} else {
baz();
} Or: if match x {
42 => true,
_ => false,
} {
bar();
} else {
baz();
} Or: if let Some(x) = if cond() {
foo();
a
} else {
b
} {
bar();
} else {
baz();
} Or: while {
let r = foo();
bar(&r);
cond(&r)
} {
baz();
} Which occur often enough in real-world code that tries to minimize mutable state. |
We decided at the lang team meeting to close this issue. We're not inclined to allow |
Sad to hear that, but if that's the case I think #961 should be closed as
well.
On Sat, Apr 8, 2017, 01:55 withoutboats <[email protected]> wrote:
We decided at the lang team meeting to close this issue. We're not inclined
to allow for or while loops to evaluate to anything but () for now. We were
all very much in agreement with Niko's earlier comment
<#1767 (comment)> that
evaluating these loops is an edge case and all of the proposed solutions
have too great a downside.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1767 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABG8JoWLSOyCTcvgqfAX0hYQn62vwTkIks5rtszZgaJpZM4KQNhr>
.
|
I would really love to have this feature, so I try to make my proposal:
Long story short, the while BOOL {BLOCK1} then {BLOCK2} This should be desugared to, and therefore have the same type and semantics with: loop {if (BOOL) {BLOCK1} else {break {BLOCK2}}} just as the usual while loop while BOOL {BLOCK1} // then {} have already and always been desugared to loop {if (BOOL) {BLOCK1} else {break {}}} It requires a bit more care for Note that the The choice of
This syntax can be used wherever the loop is meant to find something instead of to do something. Without this feature, we usually do the finding and then put the result somewhere, which is a clumsy emulation of just to find something. For example: while l<=r {
let m = (l+r)/2;
if a[m] < v {
l = m+1
} else if a[m] > v {
r = m-1
} else {
break Some(m)
}
} then {
println!("Not found");
None
} which means: loop {
if (l<=r) {
let m = (l+r)/2;
if a[m] < v {
l = m+1
} else if a[m] > v {
r = m-1
} else {
break Some(m)
}
} else {
break {
println!("Not found");
None
}
}
} Even this desugared version is cleaner than something like {
let mut result = None;
while l<=r {
let m = (l+r)/2;
if a[m]<v {
l = m+1
} else if a[m]>v {
r = m-1
} else {
result = Some(m);
break
}
}
if result==None {
println!("Not found");
}
result
} |
What if the clause is next to the conditional? let var = while BOOL else EXPR { BLOCK } You're normally reading a while loop as:
This way, And |
The type of a valued for loop would have to be Either<(), T>. let foo = for {} or else_value; let foo = match for {} {
Left => else_value,
Right(for_result) => for_result
}; This makes sense to me, because we are logically ORing together a value of a for loop with some other expression. |
Hello, I really don't know if this is ridiculous or not, but maybe P.S. : After reading again @JohnDowson last post, his I'm really eager to learn, so if my idea is completely stupid, I would really be happy to get some (honest) comments on it :) |
@tbagrel1 That also came into my mind. But it would be a breaking change. Currently this is possible:
It's stupid, and noone would do this, but in a case like this, it would break real code:
Currently this compiles, after the change it would break. But in a new edition, I think, this might be a good idea. It might have been a good idea in the first place to let simple expressions, which always return the empty tuple, to return a unusable value instead, which would force people to add semicolons after these expressions. I'm not sure, but that's what the never value ( |
@porky11 I'm not sure about unusable values; AFAIK I think it would make the closure syntax (when one returns |
@tbagrel1 In this case, it should work. But I would assume But I also got a similar idea, which is more general and should not break anything. The else case could always return the default value.
And this should be generalized to ifs as well. I often have code like this:
Maybe that's worth a new RFC |
fn main() {
for _ in 0..5 {
break
}
} would no longer be valid. Moreover, |
That's what I was expecting. Your first suggestion, that
You could still always return Especially if you want to add an
This might be useful when parsing a file, which defines values for different keywords. You could do something like this: let x = if file_data.contains_keyword("x") {
file_data.parse_values_as_vec("x")
} |
We just disagree on this point, I think Option is really more flexible and explicit than your solution. In addition, I'm not a fan of the if assignment with the implicit else case (using default), it doesn't make sense in my head when I read it (seems really counter intuitive for me) |
I love that Rust makes many code constructs into expressions. I really like the fact that I can write Since Consider (where the
Since the only way to exit the loop is through the
Now consider a
The only difference I can see here is that, unlike I suspect that
would not surprise anyone who writes Rust code. I think it would in fact feel very familiar. Also, if you then want to run code based on the But what about a
That is, if I explicitly state a value to be returned, then the type of the Importantly, all of this is opt-in, explicit, consistent and (I think) backwards compatible:
The middle case doesn't make much sense for But all this can be done with iterators! I understand where you're coming from: I'm also not in favor of needless options and complexity. But I don't like this argument in this case. This is not a needless option, and it does not add substantial complexity. IMO this is a feature that, as stated, has a very low cognitive effort to learn and use. It does not require any new keywords, it fills a gap in the syntax of the language in a (IMO) logical way (also it does not add any major complexity to the language to fill this gap), it is composable with other constructs of the language (because it uses Also iterators, as much as I love them, and a substantial part of my code is iterators, I would not use them in all cases. After all, we still have Plus, with this suggestion, we can have In the end, I think the the cost of adding some overlap in language functionality is more than compensated by the fact that it does so without almost any complexity or cognitive load, and it does allow some functionality for people who (at least in some cases) prefer loops to iterators, and also has the advantage of PS: Above it was mentioned using Footnotes
|
An extra note: I saw this argument against having a distinction between
In cases where |
Introduction
I noticed #961 didn't seem to go anywhere because of the discussion about the name of the new block. That's why I just started working on an RFC with the new block name that IMHO seemed to have the least issues, namely the
!break
one. However, I found out a lot of behavioural details where not actually decided on or discussed.What this discussion is about (spoiler: not names of keywords)
This is why I want to start a new thread to discuss these details and not the names of keywords. The names used in this issue are not meant as final keywords for an RFC. They are simply meant to allow a discussion without confusion. After the issues here are settled, the names for the keywords can be discussed again in #961 or in a new issue.
Problems
The new proposal would allow
for
andwhile
to return a value with thebreak
statement, just like proposed forloop
in #1624. We need extra behaviour becausefor
andwhile
can exit without a break. However, this can happen in two different ways. Either the loop body is never executed (for
with an empty iterator orwhile
with a false condition at the start). Or the body is executed at least once, reaches the end of the loop body or acontinue
statement and then the loop stops (e.g. because it's out of elements or the condition has become false).Solutions
For the case where the body is never executed another block is required that can return a value, this block is from here on called
noloop
. For the cases where the loop would be executed I see three main solutions. These are below with short names that are used in the rest of this issue between brackets:noloop
block as is use in the other case. ("noloop")nobreak
. ("nobreak")continue
. ("last-statement")Then there are also two combinations possible of the options option above:
nobreak
block optional and "noloop" when it is not added. ("nobreak-noloop")nobreak
block optional and "last-statement" when it is not added. ("nobreak-last-statement")My opinion on these solutions
The "noloop" option seems like the worst choice, as it does not allow to differentiate between the two different cases. The "nobreak" option seems better in this regard as it allows to have different behaviour for both cases, but is more verbose in cases where the behaviour should be the same. The "nobreak-noloop" solution allows to have different behaviour for both cases, but is concise when this is not needed.
The "last-stament" option has a big usability advantage over the previous ones, because it allows returning of a value used in the last iteration. However, this comes with the disadvantage that when this is not needed all possible endpoints need to return the same value, i.e. the end of the loop and all
continue
statements. With the "nobreak-last-statement" solution you can work around this disadvantage by using thenobreak
block.This reasoning is why I think the solutions can be ordered from most desirable to least in the following order:
Please comment if you have other ideas that could be used, or you have a different opinion en the proposed solutions.
The text was updated successfully, but these errors were encountered: