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 upAllow loops to return values other than () #352
Conversation
This comment has been minimized.
This comment has been minimized.
netvl
commented
Oct 4, 2014
|
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 :) |
This comment has been minimized.
This comment has been minimized.
|
Or just |
This comment has been minimized.
This comment has been minimized.
|
What are the semantics of breaking to a label with a value? |
This comment has been minimized.
This comment has been minimized.
jmesmon
commented
Oct 4, 2014
|
Hmmm... frankly I'd prefer a way to have loops be expressions returning something with the Iterator trait. |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
@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 |
This comment has been minimized.
This comment has been minimized.
|
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.
|
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
For Python: I think it is a relatively unknown feature there, and TBH when I first encountered it I parsed |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
assigning to @pnkfelix to shepherd |
pnkfelix
self-assigned this
Oct 9, 2014
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
@chris-morgan That's very interesting! It reminds me more than a little bit of some of the ideas in #243. |
This comment has been minimized.
This comment has been minimized.
telotortium
commented
Oct 11, 2014
|
I really find Unfortunately, |
This comment has been minimized.
This comment has been minimized.
reem
commented
Oct 11, 2014
|
Some ideas that were tossed around on IRC instead of |
This comment has been minimized.
This comment has been minimized.
|
I've thought of For what it's worth, here's a logical justification for |
This comment has been minimized.
This comment has been minimized.
|
@glaebhoerl When I first encountered the else I thought it's only executed if the loop isn't executed at all. |
This comment has been minimized.
This comment has been minimized.
|
@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? |
This comment has been minimized.
This comment has been minimized.
|
I was just thinking, if next returns a Result, then perhaps Reader could inherit from Iterator. |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
@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). |
This comment has been minimized.
This comment has been minimized.
|
Also, in the alternatives section, you note the idea of |
This comment has been minimized.
This comment has been minimized.
|
Only half serious, but we could add an |
P1start
added some commits
Oct 21, 2014
This comment has been minimized.
This comment has been minimized.
m13253
commented
Jan 18, 2015
I don't think As I was a Python programmer, I prefer the: for ... {
break Some(...)
} else {
None
}way. But I do not think "Python does this way" is the reason I suggest it. The reason I am against
And the most important, by using "else", the compiler helps us to guarantee a value must be returned. If we use Result, the check would be put to runtime. The soul of Rust is "put as more as possible security checks to compile time", isn't it? Actually I consider the programmers are good enough to understand a |
This comment has been minimized.
This comment has been minimized.
m13253
commented
Jan 18, 2015
|
@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. |
This comment has been minimized.
This comment has been minimized.
phaux
commented
Jan 19, 2015
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 |
This comment has been minimized.
This comment has been minimized.
m13253
commented
Jan 20, 2015
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! |
This comment has been minimized.
This comment has been minimized.
m13253
commented
Jan 20, 2015
|
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". |
This comment has been minimized.
This comment has been minimized.
phaux
commented
Jan 21, 2015
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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, |
This comment has been minimized.
This comment has been minimized.
|
@P1start |
This comment has been minimized.
This comment has been minimized.
|
It’s still not technically part of the Rust language. It’s not even a lang item. |
This comment has been minimized.
This comment has been minimized.
|
I feel like this is just arguing about details. The
can be argued for, it is already built into |
This comment has been minimized.
This comment has been minimized.
m13253
commented
Jan 21, 2015
|
(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;". |
This comment has been minimized.
This comment has been minimized.
phaux
commented
Jan 21, 2015
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. |
This comment has been minimized.
This comment has been minimized.
m13253
commented
Jan 21, 2015
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()
} |
This comment has been minimized.
This comment has been minimized.
|
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 comment has been minimized.
This comment has been minimized.
|
This is indeed backwards-compatible (or at least I haven’t found a backwards-incompatible subtlety yet) so long as we stick with using
|
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
ping @pnkfelix, what's the status? |
This comment has been minimized.
This comment has been minimized.
|
@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 |
This comment has been minimized.
This comment has been minimized.
|
(ah, it is possible there is a reason for |
This comment has been minimized.
This comment has been minimized.
|
Arguably |
This comment has been minimized.
This comment has been minimized.
|
@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.) |
P1start commentedOct 4, 2014
Extend
for,loop, andwhileloops to allow them to return values other than():elseclause that is evaluated if the loop ended withoutusing
break;breakexpressions to break out ofa loop with a value.
Rendered view
(See also this discuss thread.)