# Union Equality

I’m trying to represent a dynamically typed value in Zig, and need to be able to check if two of such values are equal, both in type and in value. I have a function that seems to work but want to know if there is any more efficient way to do this, rather than having a potentially long switch statement for every variant. Thanks.

``````pub const Value = union(enum) {
pub const Type = std.meta.Tag(Value);

nil,
boolean: bool,
number: f32,
// ...

pub fn equals(self: Value, other: Value) bool {
if (@as(Type, self) != @as(Type, other)) return false;
return switch (self) {
.nil => true,
.number => self.number == other.number,
.boolean => self.boolean == other.boolean,
// ...
};
}
}
``````

You can use `std.meta.eql`, or look how that is implemented:

``````        .Union => |info| {
if (info.tag_type) |UnionTag| {
const tag_a = activeTag(a);
const tag_b = activeTag(b);
if (tag_a != tag_b) return false;

inline for (info.fields) |field_info| {
if (@field(UnionTag, field_info.name) == tag_a) {
return eql(@field(a, field_info.name), @field(b, field_info.name));
}
}
return false;
}

@compileError("cannot compare untagged union type " ++ @typeName(T));
},

``````
6 Likes

Hm, isn’t that a bit of dead code? that `return false` should be `unreachable`, no?

Aside from using a library function, yes, you probably want multiple prongs of a switch statement. At least for numeric comparisons, since comparing floats is not a straightforward check on whether the bits are equal, there is also the fact that -0 and +0 are equal, NaN’s always being unequal to everything, even themselves, and maybe there’s more. So when you do `a == b` on float types it is going to give you a completely different instruction that couldn’t be used for any other type.

You could try an `inline else` though to have Zig fill in the details.

3 Likes

Thank you, I think I’ll use this.

I think this is simpler?

``````    pub fn equals(self: Value, other: Value) bool {
return switch (other) {
.nil => self == .nil,
.number => |n| self == .number and self.number == n,
.boolean => |b| self == .boolean and self.boolean == b,
// ...
};
}
``````

If you don’t mind panicking when the tags are different, you can remove the first part of the tests:

``````.number => |n| self.number == n, // will panic if self isn't .number
``````

I think this is kind of neat:

``````    pub fn equals(self: Value, other: Value) bool {
return switch (self) {
inline else => |value, tag| tag == std.meta.activeTag(other) and value == @field(other, @tagName(tag)),
};
}
``````

This works because for nil it just compares that `{}` is equal to `{}` which is true.

If any case should compare values differently like for example float, you can add it as an explicit case like this:

``````    pub fn equals(self: Value, other: Value) bool {
return switch (self) {
.float => |f| .float == std.meta.activeTag(other) and floatEqualImpl(f, other.float),
inline else => |value, tag| tag == std.meta.activeTag(other) and value == @field(other, @tagName(tag)),
};
}
``````
6 Likes

@Sze wins. Flawless victory.

1 Like