-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
new control flow keyword: result #732
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
None of those We could fix this with a companion keyword to specify which block the const result_is_to = block {
if (parsed_from.kind != parsed_to.kind) {
result true;
} else switch (parsed_from.kind) {
WindowsPath.Kind.NetworkShare => {
result !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
},
WindowsPath.Kind.Drive => {
result asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]);
},
else => unreachable,
}
}; Now here's the same thing in status quo zig: const result_is_to = while (true) : (unreachable) {
if (parsed_from.kind != parsed_to.kind) {
break true;
} else switch (parsed_from.kind) {
WindowsPath.Kind.NetworkShare => {
break !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
},
WindowsPath.Kind.Drive => {
break asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]);
},
else => unreachable,
}
}; This |
Better example of the common pattern: if (windows.GetExitCodeProcess(self.handle, &exit_code) == 0) x: {
break :x Term { .Unknown = 0 };
} else x: {
break :x Term { .Exited = @bitCast(i32, exit_code)};
}
|
I think the result keyword is the ideal solution, as it would be semantically equivalent to an implicit block return, but... you know, explicit. Anything that would require more granular control would be done with the named blocks as currently supported. |
Ok here's the original example with the "current block" semantics done properly: const result_is_to = (
if (parsed_from.kind != parsed_to.kind)
result true;
else switch (parsed_from.kind) {
WindowsPath.Kind.NetworkShare =>
result !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator),
WindowsPath.Kind.Drive =>
result asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]),
else => unreachable,
}
); Or without removing any of the curly braces, and using a minimum number of const result_is_to = {
result if (parsed_from.kind != parsed_to.kind) {
result true;
} else switch (parsed_from.kind) {
WindowsPath.Kind.NetworkShare => {
result !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
},
WindowsPath.Kind.Drive => {
result asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]);
},
else => unreachable,
}
}; Or putting the const result_is_to = {
if (parsed_from.kind != parsed_to.kind) result {
result true;
} else switch (parsed_from.kind) {
WindowsPath.Kind.NetworkShare => result {
result !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
},
WindowsPath.Kind.Drive => result {
result asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]);
},
else => unreachable,
}
}; These semantics seem pretty confusing to keep up with. I think I like my |
My understanding is that the following examples would be equivalent, the first using only expressions, and the second wrapping every expression in a block:
And with blocks around every expression
I think in these examples the semantics are a bit clearer. Your All blocks are equal, but some blocks are more equal than others =P |
I agree the current situation is awkward. I had to work that out once, and it wasn't super fun. Are any of these many new keyword proposals context sensitive? Or is there some escape for identifiers like in C# (or shell or whatnot)? Just that a lot of good identifier names are getting taken. |
You could use a closure and |
I understand this is a relatively popular proposal, but I'm going to reject it. People don't like the overly verbose const x = blk: {
break :blk foo();
}; Especially when it's one line like that. If Given that, and given that we do need the explicit syntax for breaking from an outer loop, a new keyword to break from the current block is redundant. Status quo, while verbose, allows people reading zig code to understand the control flow without being an expert in zig syntax, and, importantly, teaches zig coders how to use the explicit syntax for when they need it in those situations. This feature is strictly for convenience, and makes the language bigger. Although undeniably convenient, it's not worth the cost. |
Why can't the name of the block be optional? Then we use the implicit scoping runes of
|
Another argument for this proposal would be that #4294 could be reconsidered because to my understanding it was closed for readability reasons in situations like this: x = if (c) label: { break :label a; } else label: { break :label b; } With this proposal and #4294, it could be written as: x = if (c) { result a; } else { result b; } which in my opinion is quite decent. Edit: I still think this is a good approach, but this (reverting 629) is a better one imo. |
Wouldn't even need a new keyword if x = if (c) { break => a; } else { break => b; } I also wrote this in a another comment, but I hope it won't hurt to include it here as well. break; // normal
break => value; // when returning a value from the block
break label; // when breaking a specified label
break label => value; // when doing both of the above
// break :label <- the colon prefix would not be necessary anymore. |
In my opinion, this proposal should be reconsidered. There have been several proposals since (e.g. #2990, #5382, #5083, #9758) that aim to address the underlying problem but in my opinion none do so as well as this proposal. The main problem stemming from #629 seems to be that the ergonomics of using For example: in the Zig codebase of 2021 September 10, uses of This proposal would add value to simplify such cases with
As to instructive tradeoffs, the existence of the keyword Arguments against some alternative mitigations:
|
Here's what I end up doing relatively consistently by now in my code. adapted example from original postconst result_is_to = result_is_to: {
break :result_is_to if (parsed_from.kind != parsed_to.kind) true
else switch (parsed_from.kind) {
WindowsPath.Kind.NetworkShare => {
//say for some reason we actually need a block here
break :result_is_to !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
},
WindowsPath.Kind.Drive => asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]),
else => unreachable,
};
}; That is, in essence
While the
(Also here is a idea I thought unworthy of its own proposal now that the issue template tells you "no proposals", but it applies directly to this use case, so I'll mention too:)If this is considered "proper style", and everyone agrees on the benefits of mentioning the variable in the named
|
One awkward situation is this pattern:
In all of these situations, we just want to break from the current block.
Proposal:
result
keyword, which is likebreak
but it always breaks from the current block.The example becomes:
The text was updated successfully, but these errors were encountered: