Currently only compound expressions are labellable. The best way to label the
result of a match
or switch
expression is something like this:
let x = switch (...) {
case ... => :label {...};
case ... => :label2 {...};
};
and you have to create a new labelled compound expression for each case
.
Instead, labels should be able to be used on any expression, not just compound expressions. Like this:
let x = :label switch (...) {
case ... => ...;
};
let y = :label if (...) ... else ...;
let z = :label for (...) {...};
Should pin down the exact semantics here. For compound expressions, and control-flow expressions like if, for, switch, match, this makes sense. But there's plenty of other expressions where this could be allowed, but the behavior wouldn't be obvious. Does this just always change the expression result type?
:foo yield :foo, 0; // the yield itself has a result of int? :foo let x = { if (true) yield :foo, 0; // yields out of binding expression; does the binding expression yield an int? what's the value of x? };
The two main use-cases for something like this I can imagine would be allowing yielding from for-loops, and yielding from switch/match expressions, both of which would be very nice to have. But I'm skeptical that generalizing labellable expressions is the right way to go about this. I'm not explicitly against this proposal though, so long as the quirks are figured out.
~sebsite yeah you're right, i don't really mean "any expression". The expressions that you listed (for, if, switch, match, compound) are the only ones where this makes sense, i think in any other situation it should be an error.
What benefit does this have then over just adding special syntax to switch/match?
switch (...) :label { ... };Current language constructs work on labelled blocks, so if this were implemented, these would either need to be redone to work on other labelled expressions, or there would be multiple ways to label things, neither of which I'm a huge fan of.
// would these two be semantically equivalent? or would one of them be disallowed? let x = for () :label1 { break :label1, 0u8; }; let x = :label2 for () { break :label2, 0u8; };
imo the prior of those two examples would be disallowed, because the label is a label for a compound expression, not a
for
expression, so you can'tbreak
from it. The latter would be the right thing to do there.A better example for the first one would be
let x = for () :label1 { yield :label1, 0u8; };and in this case i don't think yielding anything except
void
from the body of a loop makes sense and this should probably be an error, since conceptually, the body is the result of one iteration of the loop. This would be equivalent tolet x = for () 0u8;which is not really sensical.
It's been a little bit, and I figured I'd write down my current two cents here to hopefully maybe get a discussion going or something:
On the surface, generalizing labels to all expressions sounds like the right thing to do. But I don't think it actually is. As ~autumnull said, it really only makes sense in compound,
if
,for
,switch
, andmatch
, andif
can even be removed from that list since compound expressions take care of that. Outside of that list, though, labels are at best useless, and at worst semantic nightmares, as in the two examples in my first comment.So, if they're not generalized to everything, then I want to be a bit picky for exactly where labels will be allowed. I don't think they should be allowed on
if
expressions, for instance, since that would duplicate the behavior of compound expressions.Part of me doesn't mind the current semantics of breaking from a labelled for-loop body, but another part of me acknowledges that the semantics are very weird: break doesn't operate on compound expressions, it operates on loops. But yet selecting the loop to break from is done by using the label of a compound expression. So I think it's reasonable to allow for-loops to be labelled. Though it'd be nice to keep the rule that yield always operates on compound expressions, and break/continue always operate on loops, so e.g. you wouldn't be allowed to yield from a for-loop, and you wouldn't be allowed to break from a compound expression. I can see why people wouldn't like this, since it kinda feels like an arbitrary separation for what's pretty much the same operation, but
break
andyield
already exist as separate things, and neither is going away, so I'd rather embrace their differences than make them identical when used with a label.
switch
andmatch
desperately need a way to be labelled. I have no strong opinions on the syntax we use here, but if I had to pick I'd say I preferswitch (0) :label {
to:label switch (0) {
, since the former makes it more clear that the label refers to the implicit compound expression of each case, rather than the switch expression itself (this distinction matters if we want to keep the rule thatyield
only operates on compound expressions). I could be persuaded otherwise though (and will very quickly concede if others disagree with me here), especially if we'd rather keep the syntax consistent across all expressions which can be labelled.So, tl;dr:
// compound label :label { yield :foo, 0; }; // must be yield, not break // for label :label for (true) { break :foo, 0; }; // must be break (or continue), not yield // yielding to a labelled for-loop body is equivalent to continuing the loop itself for (true) :label { yield :label; }; :label for (true) { continue :label; }; // switch label switch (0) :label { case => yield :label, 0; }; // or :label switch (0) { case => yield :label, 0; }; // ditto for match
I don't think they should be allowed on
if
expressions, for instance, since that would duplicate the behavior of compound expressions.it's not quite duplicating the behavior of compound expressions, since there's an
else
branch, see:if (...) :label1 { ... } else :label2 { ... };requires an extra label compared to
:label if (...) { ... } else { ... };the latter of these is far more legible imo, and makes it clearer what you're actually doing (yielding from the if/else statement)
keeping break/yield separate sounds fair enough, no strong opinions
i prefer the label as a prefix to the switch/match keyword, for consistency with
for
and compound expressions. i also think it's simpler to think about as yielding from the switch expression itself, similar to yielding from a chain of if/elses.
Something I'm just now realizing is that this grammar isn't LL(1), since for loops (and if expressions) are in a different expression class than compound/switch/match expressions. compound/switch/match expressions exist within unary-expression, whereas for/if are in the high-level expression class. The reason for the former is so compound expressions (and also switch/match) can be cast without parentheses, and also so they can be used as a binary or unary operand. This isn't possible with for/if, since it would create an ambiguity:
for (a) b: cThe cast could apply either to b or to the for expression. The cast could also be substituted with a binary expression, and the problem would be the same.
The point is, assuming we don't change any expression classes here, the parser can't know what kind of expression it should parse until it lexes the first token after the label. This is very similar to the static assert / static let issue documented in #866.
The simplest way to fix this would be to move the label in non-compound expressions:
:label { // ... }; for :label (true) { // ... }; switch :label (0) { case => // ... };I, don't hate this, I think. It's a bit unfortunate I guess, but it doesn't look bad, at least to me. Thoughts?
tbh i kinda like just keeping it as
for (...) :label { break :label; };
, since even though it's semantically a bit weird, it feels much more consistent to the end-user. it also lends itself to an interpretation of switch/match where the {} are a pseudo-compound-expression, which explains the implicit compound-expression in cases. the only changes in this case would be addingswitch (...) :label { ... }
and the equivalent for matchthat being said,
for :label (...) {
does seem better than:label for (...) {
. ig we could also doif :label (...) { ... } else { ... }
under that design
I don't really see the status quo as more consistent tbh; syntactically I guess it is, but the semantics are really weird, since you're not really selecting the thing that's being acted on. For expressions are also different in that you break from them, not yield from them. So I think it makes sense that the syntax is slightly different.
I also kinda like
switch (...) :label { ... }
, for the same reason you described. I'm not sure if I prefer it toswitch :label { ... }
though... no strong opinion either way I guess. I just want labellable switch/match expressions lol, I'll take anythingStill opposed to adding labels to if expressions, since if expressions don't have any need for it, unlike for/switch/match. When yielding from the body of an if expression, you're yielding from, well, the body, so you should be selecting the compound expression. This is different from breaking from a for expression, since for expressions have their own scope.