-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Proposal: Inline Switch Cases #7224
Comments
Another usecase: dynamic dispatch for enums and tagged unions. For example, pub fn iterate(base: *Node, index: usize) ?*Node {
inline for (@typeInfo(Tag).Enum.fields) |field| {
const tag = @intToEnum(Tag, field.value);
if (base.tag == tag) {
return @fieldParentPtr(tag.Type(), "base", base).iterate(index);
}
}
unreachable;
} With this proposal, it would simplify to this: pub fn iterate(base: *Node, index: usize) ?*Node {
return switch (base.tag) {
inline else => |tag| @fieldParentPtr(tag.Type(), "base", base).iterate(index),
};
} |
I really like this idea, just not sure if I understand the point about If we really want to straight-out disallow it for generating an absurd number of cases for |
No need for that. Since each instantiation of a case (except the first one) counts as a backwards branch, the number of cases would already be limited by the eval branch quota. |
Great idea! Another thought would be to add special syntax for the single pub fn iterate(base: *Node, index: usize) ?*Node {
inline switch (base.tag) |tag| {
return @fieldParentPtr(tag.Type(), "base", base).iterate(index);
};
} Edit : updated to use the block style, the expression style below also would work. Also instead of the |
@frmdstryr I like the idea, but the syntax you are proposing looks a bit weird. Since this is the only case when a pub fn iterate(base: *Node, index: usize) ?*Node {
return inline switch (base.tag) |tag| @fieldParentPtr(tag.Type(), "base", base).iterate(index);
} |
I really like that extension. Here are some real world cases where I would use it: |
Interesting proposal. I have used that pattern before too: https://github.com/dbandstra/oxid/blob/c6ab7570b94b6b4dfc08919b3957d0b12fc0e6ad/src/platform/draw_opengl.zig#L480-L484 |
✔️ Main proposal |
I'll expand on this a bit. While we agree that the sugared version looks cleaner for the exhaustive case, it makes it harder to special case specific items later. With the direct syntax, switch (x) { inline else => |ct| foo(ct) }
// becomes
switch (x) {
.special => doSpecial(),
inline else => |ct| foo(ct),
} But with the sugar, the natural modification is inline switch (x) |ct| foo(ct);
// becomes
inline switch (x) |ct| {
if (ct == .special) {
doSpecial();
} else {
foo(ct);
}
}
// or worse
inline switch (x) |ct| {
switch (ct) {
.special => doSpecial(),
else => foo(ct),
}
} From a maintainability standpoint, it's not clear that the sugared version is better. So we decided for language simplicity instead. For the integer types, we felt that the limit was arbitrary. The language usually doesn't care about instantiating things like this, so introducing a new limit here seemed unintuitive. If this does become a problem, we can address it then, or introduce something different (some sort of instantiation limit, similar to the eval branch quota?) to help. |
This is great! const Zag = enum { A, B, C, D };
fn foo(comptime a: Zag) void {}
fn bar(comptime a: Zag) void {}
fn someRuntimeDispatch(x: Zag) void {
switch(x) {
inline .A, .B => |aorb| foo(aorb),
inline .C, .D => |cord| bar(cord),
}
} expand to ...
switch(x) {
.A => foo(.A),
.B => foo(.B),
.C => bar(.C),
.D => bar(.D)
} would be quite nice. |
Yes, all types of cases will support inline. |
This proposal allows switch cases to be inlined and instantiated for multiple values, similar to
inline for
. It allows for limited conversion of runtime values to comptime-known values, by generating separate code blocks for each possible value.For example:
In this code, the
else
clause of the switch is instantiated twice, once for SliceTypeA and once for SliceTypeB. It generates two cases, which grab the length from different offsets into the union. The type ofval
is comptime known, but may be different in different instantiations of the case.Some more examples:
To keep things sane,
inline else
is only allowed for tagged unions and exhaustive enums, or non-exhausted enums if the_
case is specified. In the enum case, the tag is comptime known in theelse
clause. In the union case, the comptime-known tag may also be specified as a second capture (else => |val, tag|
). The type of the payload is comptime known but its value may be runtime known.For the purposes of eval branch quota, each instantiation of a case (except the first one) counts as a backwards branch (just like inline for).
The text was updated successfully, but these errors were encountered: