-
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 loops to return values other than () #352
Conversation
The idea is nice, but, FWIW, your motivation example could be rewritten much nicely without breaks: fn find(list: Vec<int>, val: int) -> Option<uint> {
for (i, v) in list.iter().enumerate() {
if *v == val {
return Some(i);
}
}
None
} so maybe it doesn't fit exactly as motivating example :) |
Or just |
What are the semantics of breaking to a label with a value? |
Hmmm... frankly I'd prefer a way to have loops be expressions returning something with the Iterator trait. |
I would have eventually proposed this myself, so let me try to motivate it:
Essentially the claim is that having all of our major constructs be useful as expressions would be really, really cool, and this seems like unambiguously the right way to do it. Iterators are nice, but if already-existing language constructs can be profitably improved and generalized, "iterators are also nice" is not really a compelling reason to miss out on doing so. |
I agree with this for the reason that it increases consistency with regard to what constructs can be used as expressions. I'd still appreciate to have clarification on what a break to a label with a value would mean though. |
@glaebhoerl FWIW, many, many Python users don't know about this, and many that do know about it find remembering the correct semantics hard. (To be clear, I'm not arguing against this feature: I have also thought about it favourably in the past, though I don't have have a particular desire for it to be added pre-1.0.)
@blaenk What do you mean by this? The fact that something like |
The fact that it is mentioned. I'm on my phone so I can't quote easily, but it says something like "a value can be written after the label of any is present," and I'm wondering what the semantics would be then.
|
Oh, I missed it, because there's no code example (that seems like something that warrants an example, for clarity). The semantics are presumably the same as a normal |
Right that makes sense, thanks Huon. For some reason I was forgetting about the semantics of labeled breaks and was thinking they jumped to the loop so that the loop continued again, something like a labled continue. |
For Python: I think it is a relatively unknown feature there, and TBH when I first encountered it I parsed |
@P1start Thanks for doing this! Can you / do you want to add being able to break out of naked blocks the same way? Another advantages is this can be used to implement @glaebhoerl try..catch, or even just use this instead. |
assigning to @pnkfelix to shepherd |
This concept and the logical extension of it to Result-based iteration is a concept that’s been around from late last year at least; here’s an almost-complete proposal I wrote for it in April: http://chrismorgan.info/blog/rust-proposal-result-based-iteration.html. At the time I decided not to take it any further (and so that article has never been published—it still isn’t, that’s a draft) as I thought it probably wasn’t going to be accepted, but actually since then we’ve headed much more to using Result for things, so possibly it’d be worth bringing it further now, though switching to Result-based iteration still seems a small stretch to convince people of… I’m surprised I didn’t do anything with |
@chris-morgan That's very interesting! It reminds me more than a little bit of some of the ideas in #243. |
I really find Unfortunately, |
Some ideas that were tossed around on IRC instead of |
I've thought of For what it's worth, here's a logical justification for |
@glaebhoerl When I first encountered the else I thought it's only executed if the loop isn't executed at all. |
@chris-morgan That's great! Is it possible to make some sort of recurring macro that would redefine break inside the body of the loop? |
I was just thinking, if next returns a Result, then perhaps Reader could inherit from Iterator. |
I found this the other day:
While in our case a (I don't understand the meaning of the Haskell type in its full generality either (would need to read the article more thoroughly) -- but instantiating |
@P1start I think this RFC could benefit from a few more examples. For example, you say that the Likewise, examples involving labelled breaks are probably warranted (see earlier comments where @huonw described such semantics). |
Also, in the alternatives section, you note the idea of |
Only half serious, but we could add an |
@blaenk wrote:
I would like to state my own idea. let outer_value = 'outer: loop {
let inner_value = 'inner: loop {
break 'outer 42; // Should go to outer_value
13 // Lexically feed inner_value a value for demonstration
}
} That is obvious that the I'm looking forward to some different idea. |
For the same reason we pay for the overhead of introducing Iterators to be able to use
That's not true. Performance would be identical to using Here are some more examples with Option:
let nums = vec![1, 3, 3, 7];
let x = for n in nums.iter() {
if n % 2 == 0 { break n; }
}.unwrap_or_default();
assert_eq!(x, 0);
let nums = vec![1, 3, 3, 7];
let x = for n in nums.iter() {
if n % 2 == 0 { break n; }
}.or(for n in nums.iter() {
if n % 3 == 0 { break n; }
}).unwrap_or_default();
assert_eq!(x, 3); Basically, you can use any Option method on for loops and pass them as an argument to functions that accept Options. This would get even better if we had proper error handling (unary |
I didn't mean performance issue. The problem is, when we use Let's say an algorithm that produces the first Fibonacci number larger than x: let result = {
let mut (a, b) = (0, 1);
loop {
if a > x {
break a;
}
(a, b) = (b, a+b);
}
} Since it is a Or if we use let result = match {
let mut (a, b) = (0, 1);
while true { // The compiler can not guarantee a value
if a > x {
break Option::Some(a);
}
(a, b) = (b, a+b);
} else {
None
}
} { // Unwrap the result by our hand
Some(result) => result, // We are doing some extra check in runtime though it is unnecessary
None => panic!()
} Then we are delaying checks to the runtime! Never delay a check that can be done at compile time to runtime! |
But what about the word "else"? Is "else" confusing? I don't think so. Take "if-else" as an example: if expr {
... // Execute this when expr is true
} else {
... // Execute this when expr is false
} Then we still can explain "while-else" similarly: while expr {
... // Execute this as long as expr keeps true
} else {
... // Execute this as soon as expr becomes false
} If proper documented, I do not think lots of people will misunderstand it as "Execute this only when the first evaluation of expr is false". |
I'd prefer if This is still kinda pointless example. See my previous examples and try to convince me that there's a cleaner way for doing it with Here's another one: Get first even number or first number divisible by 3 and turn it into a String let nums = vec![1, 3, 3, 7];
let x = for n in nums.iter() {
if n % 2 == 0 { break n; }
}.or(for n in nums.iter() {
if n % 3 == 0 { break n; }
}).map(|x| {
format!("The number was {}", x)
}).unwrap_or( "No such number".to_string() );
// x is a string "The number was 3"
I'm just discussing syntax. It would work exactly the same under the hood. |
let x;
let y = for i in some_vector.into_iter() {
if i == some_integer { x = true; break i }
} else {
x = false; -1
}; The compiler knows that let x;
let y = for i in some_vector.into_iter() {
if i == some_integer { x = true; break i }
}.unwrap_or_else(|| {
x = false; -1
}); To the compiler, |
@P1start |
It’s still not technically part of the Rust language. It’s not even a lang item. |
I feel like this is just arguing about details. The
can be argued for, it is already built into |
(I sent this reply with my mail client, sorry if the layout messed up)
If you need None, why not write a "None" by hand? If someone does not need a "None", .unwrap() takes tens of extra CPU cycles to check for the impossible "None". If you need None, write None explicitly.
When you need to return a value, write "break Some(value);", when you need to return None, write "break None;". |
It knows it will be called at most one time, because it's of type
No. Let me do it for you:
let nums = vec![1, 3, 3, 7];
let x = for n in nums.iter() {
if n % 2 == 0 { break n; }
}.or(for n in nums.iter() {
if n % 3 == 0 { break n; }
}).map(|x| {
format!("The number was {}", x)
}).unwrap_or( "No such number".to_string() );
let nums = vec![1, 3, 3, 7];
let x = for n in nums.iter() {
if n % 2 == 0 { break format!("The number was {}", n); }
}
else {
for n in nums.iter() {
if n % 3 == 0 { break format!("The number was {}", n); }
}
else {
"No such number".to_string()
}
}
assert_eq!(x, 3); Option version is much more concise and more appropriate for a functional language like rust. |
So let me show you it is possible. Just write it.
let nums = [1_i32, 3, 3, 7];
let found =
nums.iter().filter(|&x| *x % 2 == 0).next().or_else(||
nums.iter().filter(|&x| *x % 3 == 0).next()
);
match found {
Some(x) => format!("The number was {}.", x),
None => "No such number.".to_string()
}
let nums = [1_i32, 3, 3, 7];
let found = for i in nums.iter() {
if i % 2 == 0 { break Some(i) }
} else for i in nums.iter() {
if i % 3 == 0 { break Some(i) }
} else {
None
};
match found {
Some(x) => format!("The number was {}.", x),
None => "No such number.".to_string()
} |
So, I admit I have not followed the conversation here in great detail yet, but I have a couple questions for @P1start , or really, anyone who has been following the conversation:
|
This is indeed backwards-compatible (or at least I haven’t found a backwards-incompatible subtlety yet) so long as we stick with using
|
@pnkfelix I made some comments in http://discuss.rust-lang.org/t/reader-stablization-errors-and-iterators/1345/6 . But I think a better solution that what I proposed there is to temporarily make loops statements, to avoid @P1start's third point. |
@P1start @Ericson2314 thank you. I'm skeptical that we'd change the But changing the looping syntactic forms to be statements rather than expressions might be doable for 1.0, and sounds like it would give us more design freedom here. I'll try to float the idea and/or prototype that particular change. |
ping @pnkfelix, what's the status? |
@aturon I haven't had a chance to prototype anything yet. But even in the absence of concrete information about the resulting fallout, I would still recommend that we at least consider making all the looping forms statements rather than expressions. (I am pretty confident that it is sound to replace all loop-expressions with |
(ah, it is possible there is a reason for |
Arguably |
@aturon okay, now that I've filed RFC #955, I think we can probably postpone this RFC. (What we are able to do with this RFC when we get around to it will depend on how we handle RFC #955, but as stated several times in RFC #955, even if #955 itself is rejected, we could still adopt #352 as written.) |
Extend
for
,loop
, andwhile
loops to allow them to return values other than()
:else
clause that is evaluated if the loop ended withoutusing
break
;break
expressions to break out ofa loop with a value.
Rendered view
(See also this discuss thread.)