-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Restore block expressions #9758
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
Comments
More points:
|
In reference to your third point, where you bring up #8019, it's important to note that the proposal exists not because blocks do not currently suffice, but because using blocks to avoid scope pollution is ugly and tedious to write, meaning a majority if people will omit adding the scope, whereas a "with(...)" syntax or equivalent would incentivize less scope pollution, so I don't think this proposal really approaches that issue in the same way. As it pertains to your proposed reinstitution of To amend that, why not propose that simply allowing |
@InKryption thanks for the input. Answering your three comments/questions:
|
I've never understood the decision to go ahead with #629 either. The concerns voiced there all seem rather theoretical. Sure, you can forget the semicolon at the end. Or you can accidentally write I think Rust's approach is quite reasonable and doesn't take much time to get used to. On the other hand, Zig's appoach with |
I found the Rust code always very hard to read as i have to actually look at line endings instead of just not reading them. const foo = blk: {
if(use_default)
break :blk default_value;
var sum: usize = 0;
for(some_magic_list) |item| {
if(condition(item))
break :blk item.special;
sum += item.value;
}
break :blk sum;
}; Can you make a simple example with the proposed syntax how this would turn out? |
Not significantly simpler. But the control flow here is actually complex enough to need the labeled break, whereas the most common use of this feature follows the pattern const foo = blk: {
// compute result
break :blk result;
}; where the |
I'm not 100% if we're thinking on the same lane; to clarify, I'm referring to the fact that the correct code to limit the scope of variables, like this: {
var i: usize = 0;
while (i < N) : (i += 1) {...}
} is somewhat unpopular, due to the fact that it is more than a few keystrokes (depending on your IDE/text editor), so people will fairly often omit the brackets, making the Other than that, this is not so much a critique of the proposal as it is me just pointing out that one of its claims is a bit misplaced.
Is that so? My interpretation of the control flow keywords were that they are essentially just sane/controlled But from here it's just a matter of opinion I suppose. I would be in favor of using a bear |
@MasonRemaley, thank you for putting your finger on the exact issue. I agree that semicolons and commas should be invisible; they are artifacts of the grammar. You should not have to read the line endings to grok the semantics. These comments have really helped me fully understand the issue which doomed Zig's original block expression syntax. I amend the proposal to adopt the solution that semicolon be accepted as both a separator and a terminator for lists of expressions/statements, just like is now done for commas. This resolves the ambiguity. NOTE: your example is somewhat distracting because it is a labeled block which necessarily reflects exceptional control flow, whereas the proposal is about normal control flow, so I will not repeat the whole example here, but to answer briefly, under this amendment, your example could end in |
@InKryption: understood, #8019 stands on its own merits (edit: I have updated the proposal to reflect your point). And I could have added ```goto`` to that list. |
Some usage statistics that may be of interestFrom the Zig code base.
Returning values from blocks is now the primary use of It is difficult to directly search for uses of labeled breaks that really need them (due to multi-level short-circuiting) vs simple block value returns. So I just selected 25 occurrences at random and examined them manually. The results were:
(Correction: needed / not needed statistics were swapped) To summarize, about 85-90% of labeled breaks are used for returning block values, and of those, a similar proportion is used in cases where the last expression in the block could be returned implicitly. ReproducibilityThe statistics were obtained by grepping the Zig code base (4 day old master, 0c091fe), combined with manual filtering of occurrences in strings and comments.
Occurrences examined in my random sample, with my estimate of whether the break could be replaced with an implicit return:
|
@zzyxyzz thank you for the data (and the correction--I was confused before that arrived). It does seem to confirm my notion that unlabeled block expressions would find quite a bit of use. It would be very difficult to gather data, but I would imagine there are also many cases where blocks terminate with an unnecessary assignment which could be avoided with a block expression. |
I think I've mentioned this before in some other issue, but I don't remember which one it was: I'm also in favor of using
const currently = blk: { break :blk 10; };
const suggestion1 = : { break 10; }
const suggestion2 = : { break : 10; } By the way, potential syntax ambiguity for const x = : {
var some_val = : {
var x = 20;
x += 5;
break x;
};
break some_val + 20;
}; |
@YohananDiamond, thanks for the input. This issue is focused on the normal, documented, expression-oriented block structure of Zig. It seeks to pinpoint and fix the concerns which caused this behavior to be abandoned in #629. I think any tinkering with the The idea is that the reader of a program should identify normal flow by keyword-free expressions, so that labels and keywords can be reserved to clearly flag exceptional control flow. I believe the conversation so far did highlight the primary concern people had with the old Zig syntax: a block's semantics would change based on the presence or absence of a trailing semicolon. The proposed solution is to restore block expressions without that confusing quirk by always returning the value of the final statement/expression of the block. This would allow programmers to treat semicolon as a separator or a terminator just like comma is currently treated in Zig. |
In defense of Rust's no-semicolon rule, I'd like to point out that it is not really necessary to read line endings to identify implicit returns. There are other clues as well, e.g., that a value is produced but not discarded with The other advantage of the rule is that it is consistent with |
@zzyxyzz, I'm not parsing part of your comment: "Seen like that, the missing semicolon is merely an additional clue, and I don't see how not having it would improve readability." I agree that
|
@acarrico If, on the other hand, the semicolon is given the precise semantics of terminating a void-valued statement, then the lack of it at the end of the last line of the block becomes a reliable visual indicator of a value being returned. Although, as I pointed out in my previous comment, even that is not strictly necessary either for the reader or the compiler. |
"Terminating every statement with a semicolon is firmly established style in C-like languages, and allowing the last statement to be either terminated or unterminated would just be a second way to do the same exact thing" @zzyxyzz, in C, blocks are statements, but in Zig blocks are expressions. Personally, I don't care if a semicolon is required or optional after the last expression, but that semicolon is just syntax terminating or separating the elements of the block: statements are void valued expressions. There is no further distinction, so the semicolon's presence or absence shouldn't change the meaning of the block expression. |
Sorry! Accidentally "closed-with-comment" the issue. |
@zzyxyzz, "But why?" Only because there is no agreement on if the semicolon should be present or absent, and the answer doesn't matter. Allowing it to be optional in {x; y; z;} is similar to the comma in (x, y, z,) which some people hate and some people like. Personally I think of the semicolon as a terminator, and the comma as a separator, but it is a matter of syntax, not semantics. I would have no problem requiring the semicolon to terminate expressions if people agree (to be honest it is actually my preference since I am used to C). I would accept either syntax as long as we achieve the documented behavior: "Blocks are expressions." That trailing semicolon shouldn't turn them into statements. |
@acarrico Re lists: Making the last comma optional in lists makes sense, IMHO, because lists (and field declarations) are commonly written in two different layouts: One style is to put the list on one line, as in
In this case the trailing comma is preferable, because it feels a bit more like a terminator, and also reduces noise in diffs when you add or remove fields. Statements, on the other hand, overwhelmingly use the one-per-line style (at least in C-like languages), where the terminator view is more appropriate. So I don't see much practical need for treating the semicolon as a separator. But this is just my opinion, and we are heading into bikeshedding territory here 😄. |
@andrewrk Is there a reason for the closure? |
It's closed because the proposal has been rejected. The number of upvotes a proposal has is meaningless. I've already considered (and implemented) block expressions, and then removed them from the language, and I also reconsidered adding them back when reading the comments in this issue. Ultimately a decision needs to be made. And here it is. |
@andrewrk--thanks for considering the proposal! |
I understand this issue is closed, but as of 37df6ba the Zig repository contains 1087 instances of labelled blocks whose label is I argue that in almost all of these cases the label is redundant, serving only as noise, and it's requirement increases friction unnecessarily. The implementation proposed by yohannd1, where block expressions start with |
Learning Zig, I expected blocks to be expressions, which is how they are documented in the manual: "Blocks are used to limit the scope of variable declarations. Blocks are expressions."
But unlabeled blocks are not currently treated as expressions.
Digging in, I discovered that Zig apparently did have an expression oriented block structure back in 2017, but this was removed in favor of statement oriented blocks in #629. I propose that (unlabeled) block expressions should be returned to the language:
It would fix the current documentation bug (blocks currently act as statements, not expressions).
Normal (non-exceptional) control flow would be more pleasant to read and write. The status quo (using a label to give a block a value) feels a lot like assembly language. There is even a (strange) proposal to get rid of block statements by enforcing this labeling, Proposal: Disallow unlabeled blocks as block expressions #9059.
In the issue, Introduce expression-scoped variables #8019, people are clamoring for expression-scoped variables, but that is exactly what blocks do.
There would be no reason to introduce a new(Edit: other reasons exist)with
syntax, as proposed, if blocks were treated as expressions.Probably the best reason to restore block expressions is that conditional expressions (
switch
andif
) are clunky without block expressions (various issues are more or less related to this point).Zig is a block structured language. Expressions are easier to reason about than statements. Is there any reason not to do this? My impression is that the change which removed block expressions was mostly about cleaning up exceptional control flow. Was removing block expressions a necessary part of this cleanup, or more of an unintended consequence?
Just to illustrate syntax:
Currently:
Proposal:
one orboth of these should succeed:Whichever option best fits the grammar and semantics, it seems like the first option with no semicolon was the old syntax.(as amended below: semicolons should be accepted as both a separator and a terminator for lists of expressions/statements)The text was updated successfully, but these errors were encountered: