-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
organize the concepts of enum and union #618
Comments
Low level data fiddling is big use case for C unions. "Debug safety" would be harmful here. One example is using tagged pointers. In some situations the tag selector is already present somewhere else. |
Zig unions with safety are compatible with a tag selector being somewhere else. The safety tag is omitted in release-fast builds. You're going to have to elaborate on exactly how safety would be harmful because it prevents only what would be undefined behavior. I suspect that the "low level data fiddling" you are thinking of would be a Type Based Alias Analysis violation. |
Or are we talking about a situation where the exact size and layout of the fields is important, in which case use a |
I am guessing (it is not documented) that if there are safety checks one could not do the "casting via union" as in C. Example: advanced allocator with memory guards around the block and with lot of metadata inside. This can be manipulated by incrementing/decrementing pointer, but as this way is closed in Zig, raw unions may be usable here. Another example is easy extraction of lowest bits from a tagged pointer and the true pointer value itself. I think all of above could be accomplished via |
This is undefined behavior, at least in C99. It's not more cumbersome than it needs to be. You modify which field of the union is active by assigning a new value to the entire union, rather than a specific field. The use case with an advanced allocator is handled, no problem. |
I'll probably go the In this thread I got concerned by the emphasize on safety by limiting expressibility. Things like leaks, runaway pointers or nulls can be caught trivially, complex code is more defiant to change. Couple of ideas about the enums:
No need to invent yet another name, and the enum definition is next to supposed use, not somewhere far away, prone to misuse.
|
Could someone provide some examples? I think I get it, but... I have been wrong more than once :-) Other languages show sum types with different syntax:
So the solution above from @andrewrk is going to be:
Is that close? How does switch work with this? The ASTNode example is pretty common. @PavelVozenilek you have some good examples there. I have made use of tagged pointers in some code (particularly when implementing VMs such as in Smalltalk or Java). While it is not portable to use unions, since the code is supposed to be very, very tied to a specific machine type, this is not a problem in practice. One thing I would like to see is the ability to convert something into a bit array such that I can use it to access specific bits directly. Then tagged pointers would be a breeze. |
What will happen to the implicit error union types, e.g. |
Instead of const Letter = enum {
A,
B,
C,
};
const Payload = union(Letter) {
A: i32,
B: f64,
C: bool,
}; This gives the union a tag field, which has the type of the enum given. Like a union(T) implicitly casts to T. So,
Error unions are independent from this issue. |
In the example:
Has the |
Does the enum have to be defined ahead of time or can it be inferred from the union definition, as in:
Also this syntax does not give name to the alternatives, is that intentional? Can you do |
The enum type has a separate use:
const x = Payload { .B = 5.35 };
const y = x.B;
assert(Letter(x) == Letter.B); At least for now, the enum will have to be defined ahead of time. |
Also make it so that enums support signed integer tag types. |
On second thought, I'm going to accept @jido's proposal. So you can do: const Payload = union(enum) {
A: i32,
B: f64,
C: bool,
} And this automatically creates the enum. You can also do: const Payload = union(enum(u32)) {
A: i32 = 3,
B: f64 = 10,
C: bool = 100,
} And now you've configured the integer tag type of the enum, and specified the tag values. Then you can access the automatically created enum type with A |
Ha, and what if you want to give a default value to the members of the union? That is what it looks like with the latter syntax. It is confusing. |
This syntax
feels really strange. Edit: the visual pattern
should be reserved for variable/const definition with initialization. |
You can't do that. Neither structs nor unions support giving a default value to a field.
The other option we have is: if you want to change the enum tag values, you would have to specify the enum separately. |
enum
which can be "dumb" enums where it's just named number values.extern enum
makes sense in this caseenum
can also have associated types, which makes it a "tagged union". This is very close to unions, the only difference is that with unions the tag is a secret field only used by debug safety. It's silly that the initialization syntax for these kind of enums is different than for unions. (and the union init syntax is better since it mirrors structs.)extern enum
does not make sense in this caseunion
works like a C union, except we have debug safety to make sure you don't access the wrong fieldextern union
maps to a C union, and disables the safety field.Bottom line:
enum
does too much and it acts too much likeunion
. It violates "only one way to do things".Solution:
enum
is only for "dumb" enums. you can specify the tag type and tag values.enum union
which is aunion
that always has the tag field.extern enum union
works, it makes (in C)struct Foo { enum { ... } tag; union { ... } payload; }
. Init syntax is the same asunion
.enum
.switch
to work withenum union
, like how it currently does with enums with payloads.enum union
. You have to usevoid
when you wantvoid
.enum union
looks exactly like aunion
. You might have to useFoo { .field = {} }
for void types.enum union
creates a sub-type which is a dumb enum type. It's the type of the tag. You get a value of this type if you doFoo.field
.enum union
can implicitly cast to itsenum
tag type. This means you can do e.g.foo == Foo.field
.Now there's only one way to do things. If you need a dumb enum, use
enum
. Otherwise ifenum union
fits your use case, use that. Otherwise, use the flexibility thatunion
provides.The text was updated successfully, but these errors were encountered: