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 () #961
Comments
pnkfelix
added
the
postponed
label
Mar 10, 2015
P1start
referenced this issue
Oct 8, 2015
Closed
Add a `let...else` expression, similar to Swift's `guard let...else` #1303
glaebhoerl
referenced this issue
Nov 26, 2015
Open
Event indicators: Extended parametric labeled scope exit points #1380
This comment has been minimized.
This comment has been minimized.
JelteF
commented
Jan 23, 2016
|
I'm just starting with rust, but why would loops not just return their last statement like everything else seems to do, functions,
The |
This comment has been minimized.
This comment has been minimized.
|
@JelteF the problem is that a loop without a return or break statement will never return, for that reason you cannot end your loop on an expression. |
This comment has been minimized.
This comment has been minimized.
|
@ticki |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats Yeah, that's right. |
This comment has been minimized.
This comment has been minimized.
KalitaAlexey
commented
Jan 25, 2016
|
You want to make loops return value of last expression? You want to make it implicit without any break?
I think this is ugly. |
This comment has been minimized.
This comment has been minimized.
|
Update: never mind the note below (which is preserved just so the responses that follow continue to make sense).
@JelteF another reason to not just use the last expression in the loop-form's body as its return value is that it would be a breaking change: loop-form bodies today are allowed to end with an expression that is evaluated and then discarded. Returning the last expression implicitly would change the dynamic extent of the returned value, which in turn would change the lifetime associated with it and its r-value temporaries. And that change would probably inject borrowck errors into existing stable code. (The alternative of solely using |
This comment has been minimized.
This comment has been minimized.
JelteF
commented
Jan 25, 2016
|
@ticki You might have misunderstood. I only said we did not need the @KalitaAlexey I did suggest code like that, but the break could still be used. It is a very good point that you are making though. I had not thought about the case that the loop would never be evaluated. It seems you are right that the @pnkfelix I'm not sure what the breaking change is, since the check for the type of the return value could simply be skipped in cases where it is not saved in a value. |
This comment has been minimized.
This comment has been minimized.
Please, no. Python has this, and every time I encountered it I had to jump into REPL and see when this I’m not that opposed to being able to “return” something from the loop with a |
This comment has been minimized.
This comment has been minimized.
golddranks
commented
Jan 25, 2016
|
I think that an else block is strictly more expressive than returning the final expression and only thing that makes sense in the presence of value-returning breaks. If the value of final expression is going to be returned, why should the evaluation of the last run of the loop be any different from any other run? And are the final expressions (given that there is no side effects) evaluated for nothing on all the other runs? Optimizing them off becomes then burden on the compiler. If the value-returning break isn't hit, then there needs to be an alternative path that returns a value of the same type. It doesn't have to be named "else", but I think that's a sensible name. |
This comment has been minimized.
This comment has been minimized.
erikjohnston
commented
Jan 25, 2016
This bites me every time too, mainly because while useful its a rarely used feature. Maybe a better/more accurate name would help? (Nothing immediately springs to mind though.) Or perhaps something slightly different, like having a for x in iterator {
if foo(x) {
break "yes!";
}
} default {
"awww :("
}Where the default expression is evaluated either if |
This comment has been minimized.
This comment has been minimized.
JelteF
commented
Jan 25, 2016
|
I agree that the I think another name would indeed be good. Something that comes to my mind would simply be PS. I retract my initial proposal about using the last statement instead of the |
This comment has been minimized.
This comment has been minimized.
|
FWIW I think the best way to move forward on this, incrementally, would be to start by only allowing |
This comment has been minimized.
This comment has been minimized.
JelteF
commented
Jan 25, 2016
|
@glaebhoerl |
This comment has been minimized.
This comment has been minimized.
|
This can kind-of be already done as {
let _RET;
for x in iter {
if pred(x) {
_RET = x;
break;
}
}
_RET
} |
This comment has been minimized.
This comment has been minimized.
|
Yet again, @glaebhoerl says exactly what I was going to say :). Somebody want to make an RFC for this, I'd be willing to help? @JelteF heh, that's kinda the point! Once people see how nice this is, there will be more motivation to actually reach a consensus on break-with-value for other types of loops (and maybe even normal blocks!). |
This comment has been minimized.
This comment has been minimized.
JelteF
commented
Jan 25, 2016
|
@arielb1 Of course it can be done, but the point is that this: let a = for x in 1..4 {
if x == 2 {
break x
}
} nobreak {
0
}looks much cleaner than this: let a = {
let mut _ret = 0;
for x in 1..4 {
if x == 2 {
_ret = x;
break;
}
}
_ret
}@Ericson2314 It seems that if the only consensus that needs to be reached is the naming, it could be solved rather quickly. It would be weird to hurry an incomplete proposal, if all that needs to be done is pick a name for a statement. |
This comment has been minimized.
This comment has been minimized.
|
@JelteF Well I'll grant you that originally there were more ideas, but because #955 did not happen <some loop> {
...
break; // as opposed to break ();
...
} else {
my_fun() // returns ()
};@nagisa Anyone playing around will notice that the type checker will require |
This comment has been minimized.
This comment has been minimized.
JelteF
commented
Jan 25, 2016
|
@Ericson2314 I don't see a reason why that should not work. In Python it is not an expression and it still has a use. Namely handeling the edge case when the loop does not break. A simple example can be found here: https://shahriar.svbtle.com/pythons-else-clause-in-loops#but-why for x in data:
if meets_condition(x):
break
else:
# raise error or do additional processing vs condition_is_met = False
for x in data:
if meets_condition(x):
condition_is_met = True
if not condition_is_met:
# raise error or do additional processingAs for your comment @nagisa. In this case it might not be directly clear what the |
This comment has been minimized.
This comment has been minimized.
|
I passionately hate the idea itself of having an I don’t want to see any of that weirdness in Rust just because Python has it. One might argue for a new keyword, but that’s ain’t happening either, because of backwards compatibility. EDIT: All looping constructs have trivial desugarings into a plain old |
This comment has been minimized.
This comment has been minimized.
|
@nagisa Sure, but the desugarings of |
This comment has been minimized.
This comment has been minimized.
JelteF
commented
Jan 25, 2016
|
@nagisa |
This comment has been minimized.
This comment has been minimized.
|
@nagisa Personally, it reminds me of the base case of a fold, and thus actually feels quite elegant. |
This comment has been minimized.
This comment has been minimized.
golddranks
commented
Jan 26, 2016
|
@nagisa If all looping constructs desugar into I just came to think of another possibility for
This is nice in the sense that it doesn't need any new keywords or reusing old keywords in surprising way. |
This comment has been minimized.
This comment has been minimized.
KalitaAlexey
commented
Jan 26, 2016
|
@golddranks But that's confusing. It is like functional programming but ugly. |
This comment has been minimized.
This comment has been minimized.
golddranks
commented
Jan 26, 2016
|
Another note: sometimes I've written a loop that is expected to set some outer state inside the loop. But because setting the state (in that particulal case was) may be expensive, you might want to avoid setting a "default" state before running the loop. But this results in the fact that the control flow analysis can't be sure if the state is set in the end, since it's possible that the loop runs 0 times. I have to make a boolean flag to check, and even then, if the analysis isn't super smart, it won't be sure. Having a |
This comment has been minimized.
This comment has been minimized.
fstirlitz
commented
Mar 30, 2017
|
That article says the top type is one that can represent every possible value. This is something different: making every type convertible into Right now, not even code like this is accepted: let x: () = 5;
let y = 5 as ();I don't see a reason why it shouldn't. It's valid (even if not very useful). Casting to |
This comment has been minimized.
This comment has been minimized.
taralx
commented
Mar 31, 2017
|
@fstirlitz Making the type of for-without-break anything other than () is a breaking change. Making everything coerce into () restricts the type system in subtle and difficult-to-fix ways. If you want the nitty-gritty I recommend Stephen Dolan's paper on Algebraic Subtyping. |
This comment has been minimized.
This comment has been minimized.
le-jzr
commented
Mar 31, 2017
•
|
Right now, not even code like this is accepted:
let x: () = 5;let y = 5 as ();
I don't see a reason why it shouldn't. It's valid (even if not very
useful). Casting to () can be always defined as ignoring the casted value
and always returning ().
Rust's safety heavily depends on very smart people being able to prove
important claims about how the types work and interact with every other
language feature. I'd guess that adding mathematically horrid things like
this would make that work much harder, and drive away all the people whose
input Rust depends on.
|
This comment has been minimized.
This comment has been minimized.
fstirlitz
commented
Mar 31, 2017
|
I don't understand what is so supposedly 'horrid' about making a unit type into a terminal object in the diagram of possible type casts. It's mathematically sound. The RFC defining the |
This comment has been minimized.
This comment has been minimized.
|
I agree, it would be perfectly sound theoretically to allow any type to coerce into (It's an interesting question why this kind of error-proneness seems to be the case for coercing into |
This comment has been minimized.
This comment has been minimized.
le-jzr
commented
Mar 31, 2017
•
|
! is naturally coercible into anything because it has no values. Every value of ! is also a value of any other type. In order for () to be a supertype of everything (and a dual of !), it would need to contain ALL the values representible in Rust. In other words, traditional intuition is that when something coerces, there is no loss of information about the value. This clearly doesn't hold here. |
This comment has been minimized.
This comment has been minimized.
le-jzr
commented
Mar 31, 2017
|
C is broken in so many more important ways that casts into (void) are hardly worth mentioning. |
This comment has been minimized.
This comment has been minimized.
They're not duals; coercing into |
This comment has been minimized.
This comment has been minimized.
le-jzr
commented
Mar 31, 2017
That's not it either. To coerce a value at runtime, you need to have the value, and |
This comment has been minimized.
This comment has been minimized.
|
(tired so may not be the best explanation) The reason that they're duals is that In other words, fn weaken<T>(t: T) -> () {
t;
}
fn reverse_weaken<T>(bot: !) -> T {
bot
}(thanks to @eternaleye for help with phrasing) |
This comment has been minimized.
This comment has been minimized.
le-jzr
commented
Apr 1, 2017
•
|
On the flipside, coercing from A top type, however, is a type that can hold any value. Note that a true top type cannot exist in Rust, because of value semantics and lack of automatic boxing -- values in Rust can have arbitrary static length, and a top type would need to represent them all, so no amount of memory would suffice for a single value of the top type. |
This comment has been minimized.
This comment has been minimized.
le-jzr
commented
Apr 1, 2017
•
|
While many people here make arguments out of misunderstandings about the type system, this does not mean the original proposal is without merit. Let's stop talking about breaking the type system and focus on the original problem instead? |
This comment has been minimized.
This comment has been minimized.
|
No,
As for @fstirlitz's suggestion of allowing any type to be implicitly convertible to As for solving break-with-value from |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Apr 1, 2017
|
If folks still really want these, then we might consider adding a variant of
And/or a short circuiting
At least these treat the borrowing problem that makes |
This comment has been minimized.
This comment has been minimized.
|
@le-jzr I feel like you misunderstand basic type theory. I am not talking about the Python or Java interpretation of types. The wikipedia article mentions it wrt propositional calculus:
Just because my comment uses a phrasing you are not used to, does not make my point invalid. In classical type theory, there is no "top type" as used by the GC languages, because there's no way to construct such a type. Please don't assume I don't understand. It makes me very cranky. |
This comment has been minimized.
This comment has been minimized.
comex
commented
Apr 1, 2017
|
@ubsan But by that interpretation, any type that corresponds to a formula that is true - that is, any inhabited type - should be a top type. In other words, you can also implement: fn weaken<T>(t: T) -> i32 {
4
}You could argue that this doesn't merely throw away information but also adds it - because this |
This comment has been minimized.
This comment has been minimized.
|
Regardless of how () and ! should behave, and whatever "top type" does or doesn't mean, allowing for loops to return a value seems like a really weak argument for making a change as fundamental and far-reaching as enabling casting any type into (). |
This comment has been minimized.
This comment has been minimized.
|
@comex On the
For more information theory, I recommend Roshan James' "The computational content of isomorphisms". Otoh, I don't want coercions to |
This comment has been minimized.
This comment has been minimized.
comex
commented
Apr 2, 2017
|
@ubsan I said "homomorphic" when I meant "isomorphic", but otherwise, yes, I know. Actually, I'm not sure I disagree with you at all. But my point was:
(I say "top-like" rather than "top" because of course it's not actually a supertype of anything; Rust doesn't have much true subtyping.) |
This comment has been minimized.
This comment has been minimized.
|
I'm not sure that "top-like" matters very much, but the homomorphisms do (and I mean homomorphism: a bijective map which in some sense preserves operations/meaning). @ubsan I assume you mean Duals in the sense that they have the same number of values? Yes, but only if Duals in the sense that there exists an isomorphism between the type I am not averse to such concepts being explored and potentially even being accepted, but I don't think here is the place. I also don't think it would solve this problem: if I am not so happy defining a homomorphism from |
This comment has been minimized.
This comment has been minimized.
|
@dhardy no, duals does not mean isomorphic, it means "(of a theorem, expression, etc.) related to another by the interchange of particular pairs of terms". i.e., @comex the important thing is that, given referential transparency, there is exactly one implementation of |
This comment has been minimized.
This comment has been minimized.
le-jzr
commented
Apr 2, 2017
•
|
@ubsan Your arguments are entirely invalid, and I don't know how to explain why any more than I already did. My best guess is that you consider "top" to mean something entirely different than the intended meaning in context of type theory. As for "dual", I can't even guess what formalism you are coming from, as the concept of duality means essentially a completely different thing in each corner of mathematics it's used. That does not implicitly make the definition invalid (after all, same or similar names are used in many contexts for different things), but it does make it irrelevant in this context. Furthermore, fact remains that Rust does not include any way to convert a value from any type to So while we can go back and forth arguing what a top type is, that conversation is meaningless. It was suggested that " |
This comment has been minimized.
This comment has been minimized.
Huh? @ubsan is using "top" the way it's always used in type theory. The top type is the type which can hold values of any other type.
Given that we're talking about type theory I'd assume that @ubsan means dual in the category-theoretic sense, in which case
What's the difference between "convert" and "discard"?
Correct. They would all be considered the top type if we had subtyping and put them at the top of the type hierarchy.
Like what? I'm not saying it's a good idea from a language-design point of view, but it's type-theoretically sound.
No it doesn't. It doesn't hurt the language to implicitly coerce to
From the Wikipedia article on the top type:
ie. |
This comment has been minimized.
This comment has been minimized.
|
@canndrew are you advocating @fstirlitz's proposal or just defending @ubsan's logic? I doesn't appear to me that everyone agrees a value-less type (like But I end with the same point as my last post: I am not so happy allowing an implicit conversion from |
This comment has been minimized.
This comment has been minimized.
le-jzr
commented
Apr 7, 2017
|
I replied in https://internals.rust-lang.org/t/on-type-systems-and-nature-of-the-top-type/5053. |
This comment has been minimized.
This comment has been minimized.
|
Per #1767 I'm closing this issue. We now support We're open to revisiting this some day if conditions change a lot. For example, possible now that |
withoutboats
closed this
Apr 8, 2017
This comment has been minimized.
This comment has been minimized.
orent
commented
Apr 20, 2017
|
In case this ever gets revisited, how about combining @glaebhoerl's idea of moving the break into the block and using the 'final' keyword as proposed by @canndrew:
I would find the meaning obvious enough reading this code even if I was not familiar with the feature (something I can't say about python's for/else). |
pnkfelix commentedMar 10, 2015
Extend for, loop, and while loops to allow them to return values other than ():
Proposed in #352
Some discussion of future-proofing is available in #955