const TaggedUnion = union(enum) {
one: void,
two: void,
};
const p = @import("std").debug.print;
pub fn main() void {
const x = TaggedUnion{.one = {}};
const y = TaggedUnion{.two = {}};
p("{}\n", .{x == .one}); // prints 'true'
p("{}\n", .{x == .two}); // prints 'false'
p("{}\n", .{y == .one}); // prints 'false'
p("{}\n", .{y == .two}); // prints 'true'
//p("{}\n", .{x == x}); // error: operator == not allowed for type 'TaggedUnion'
//p("{}\n", .{x == y}); // error: operator == not allowed for type 'TaggedUnion'
//p("{}\n", .{y == x}); // error: operator == not allowed for type 'TaggedUnion'
}
I learned that
comparing a whole instance of a tagged union with it’s kind is allowed
comparing two instances of a tagged union is not allowed
I do know that “tagged unions coerce to their tag type” (and this allows switching over tagged union instance as if we are switching over it’s kind), but this seems to me a bit contradictory.
Slices, for example, have kinda “automatic” len field.
Why don’t tagged unions have tag field?
Or maybe, there should be @tag builtin?
Is there some way to compare tags of two tagged union instances directly?..
So these two are semantically the same.
And It gives (if we forget for a minute about coersion) an impression as if x and activeTag(x) are (kinda) same things… still I feel something is wrong with it.
What if there was no tagged uniion → it’s tag coercion?
We’d just write switch (activeTag(u)) and similar things. Why this coercion?
I mean this coercion happens automatically when comparing with tags, so we can write both x == .one and activeTag(x) == .one, it’s the same, two ways of expressing the same. Without this coercion we’d always extract tags with activeTag()… or with x.tag (akin slice.len)… or with @tag(x). There’d be only one way.
I see. I suppose it is just the particular design decision that union tags are accessed via coercion.
While x == .one and activeTag(x) == .one are semantically the same, the coercion is just happening differently. In x == .one, x is complicitly coerced to it’s tag type due to the inferred type of .one, and with activeTag the coercion is made explicitly.
This is necessary in the x == y case as there’s no context that implies that either should be coerced to the tag type, although perhaps the compiler could be taught to infer that for this case.
I understand it but do not understand why such decision was made.
To me personally it would be quite good to extract tags explicitly everywhere.
Not a big deal to type some extra letters . And it would be more readable imho.
Ok.
Let it be here:
const TaggedUnion = union(enum) {
one: void,
two: void,
};
const p = @import("std").debug.print;
const activeTag = @import("std").meta.activeTag;
pub fn main() void {
const x = TaggedUnion{.one = {}};
const y = TaggedUnion{.two = {}};
// you don't have to use 'activeTag' in switch
switch (x) {
.one => |_| p("x is ONE\n", .{}), // *
.two => |_| p("x is TWO\n", .{}),
}
// but you may if you wish
switch (activeTag(y)) {
.one => |_| p("y is ONE\n", .{}),
.two => |_| p("y is TWO\n", .{}), // *
}
// same when comparing unions with tags
// you may use activeTag or you may not
p("{}\n", .{x == .one}); // prints 'true'
p("{}\n", .{activeTag(y) == .two}); // prints 'true'
// but when comparing two unions
// you HAVE to extract tags manually
p("{}\n", .{activeTag(x) == activeTag(y)}); // ok
// p("{}\n", .{x == y}); // error: operator == not allowed for type 'TaggedUnion'
}
Initially I placed this topic into “Explain” category.
But after @n0s4 told about activeTag() I changed my mind,
moved it to “Help” and marked @n0s4’s reply as solution
(and let it be so, it’s really is, when one needs to compare tags by some reason).
But still automatic tagged union to tag coercion puzzles me…
For me explicittu.tag (or similar) everywhere would be better.
And now I have an itch to open new topic (“Why tagged union to tag coercion?” or so),
in “Explain”, but I’m not sure whether it’s worth it or not.