# Nested tagged unions

**URL:** <https://ziggit.dev/t/nested-tagged-unions/6173>\
**Category:** Help\
**Tags:** language, standard-library\
**Created:** [September 28, 2024, 7:30am UTC](https://ziggit.dev/t/nested-tagged-unions/6173 "2024-09-28T07:30:53Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 7:30am UTC](https://ziggit.dev/t/nested-tagged-unions/6173/1 "2024-09-28T07:30:53Z")

</div>

This is a continuation of [this question](https://ziggit.dev/t/comparing-tags-of-tagged-unions/6154). Suppose I want to describe results (values) of some measurements. We have physical quantities, each quantity (length, mass, force etc) can be measured in various units (grams, kilograms etc for mass and whatnot). Of course, this can be modeled in a number of ways, I constructed this (as an exercise with… say, “multi-level” tagged unions):

```zig
const p = @import("std").debug.print;
const tag = @import("std").meta.activeTag;

const Value = struct {
    const Quantity = union(enum) {
        const DistUnit = union(enum) {
            mm: void,
             m: void,
            km: void,
        };
        const TimeUnit = union(enum) {
            s: void,
          min: void,
            h: void
        };
        const MassUnit = union(enum) {
            mg: void,
             g: void,
            kg: void,
        };
        dist_in: DistUnit,
        time_in: TimeUnit,
        mass_in: MassUnit,
    };

    name: []const u8,
    q: Quantity,
    v: f32,

    fn quantityName(v: Value) []const u8 {
        return @tagName(v.q);
    }

    fn unitName(v: Value) []const u8 {
        return switch (v.q) {
            inline else => |u| @tagName(u),
        };
    }

    fn isEquatableTo(this: Value, other: Value) bool {

        if (tag(this.q) != tag(other.q))
            return false;

// return switch (this.q) { // failure
// inline else => |tu| tu,
// } == switch (other.q) {
// inline else => |ou| ou,
// };

// const aa = switch (this.q) { // failure
// inline else => |u| u,
// };
// const bb = switch (other.q) {
// inline else => |u| u,
// };
// return aa == bb;

        return true;
    }
};

```

In `Value.isEquatableTo()` function I want to check if two values are “the same” meaning

- they are of same quantity (mass is not distance for ex)
- they measured in same units (kg is not mg for ex)

First part is ok, I stumbled at the second. I messed with `inline else`, but failed to compare unit tags. How to do this?!?

Also there is a thing which I was not able to understand.  
Here is `main` function:

```zig
pub fn main() !void {
    const v = Value{.name = "tower-height", .q = .{.dist_in = .m}, .v = 27.5};
    const a = Value{.name = "apple-mass", .q = .{.mass_in = .kg}, .v = 0.2};
    p("{s} is {} {s}\n", .{v.name, v.v, v.unitName()});
    p("{s} is {} {s}\n", .{a.name, a.v, a.unitName()});
    p("{} // {s} {s}\n", .{v.isEquatableTo(a), v.quantityName(), a.quantityName()});
// p("{} // {s} {s}\n", .{v.isEquatableTo(v), v.quantityName(), v.quantityName()});

    const x = switch (v.q) {
        inline else => |u| u,
    };
    p("{}\n", .{x});
}

```

The last part with `switch` works fine here, but when similar statement is used in `isEquatableTo`, compiler says:

```zig
4.zig:57:20: error: incompatible types: '4.Value.Quantity.DistUnit' and '4.Value.Quantity.TimeUnit'
        const aa = switch (this.q) { // failure
                   ^ ~~~~~
4.zig:58:32: note: type '4.Value.Quantity.DistUnit' here
            inline else => |u| u,
                               ^
4.zig:58:32: note: type '4.Value.Quantity.TimeUnit' here
            inline else => |u| u,
                               ^

```

Why is it so?

---

<div class="post-metadata">

**Author:** ![IntegratedQuantum](https://ziggit.dev/user_avatar/ziggit.dev/integratedquantum/32/782_2.png) [@IntegratedQuantum](https://ziggit.dev/u/IntegratedQuantum)\
**Post date:** [September 28, 2024, 9:07am UTC](https://ziggit.dev/t/nested-tagged-unions/6173/2 "2024-09-28T09:07:43Z")

</div>

> [@dee0xeed](#):
>
> The last part with `switch` works fine here, but when similar statement is used in `isEquatableTo`

It works fine here because v.q is comptime-known, so it only evaluates one branch of the switch statement. Whereas if it was runtime known (try changing `v` to var), like inside a function call, then you cannot assign the result to a variable because there are three different result types.

As for the actual problem: The key is to evaluate both unions inside the same switch statement. You are allowed to do this since you checked beforehand that they have the same tag. I think something like this would work:

```zig
...
switch (tag(v.q)) {
    inline else => |t| return @field(this, @tagName(t)) == @field(other, @tagName(t)),
}

```

Another note:

```zig
const TimeUnit = union(enum) {
    s: void,
    min: void,
    h: void
};

```

If all your union types are void, why not just use an enum? This should make it a bit easier to deal with.

```zig
const TimeUnit = enum {s, min, h};

```

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 9:11am UTC](https://ziggit.dev/t/nested-tagged-unions/6173/3 "2024-09-28T09:11:39Z")

</div>

> [@IntegratedQuantum](#):
>
> If all your union types are void, why not just use an enum?

I have such “model”, just for the moment for some irrational reason I wanted to experiment with this model. 🙂

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 10:41am UTC](https://ziggit.dev/t/nested-tagged-unions/6173/4 "2024-09-28T10:41:35Z")

</div>

> [@IntegratedQuantum](#):
>
> I think something like this would work:

Probably you meant

```zig
    fn isEquatableTo(this: Value, other: Value) bool {

        if (tag(this.q) != tag(other.q))
            return false;

        switch (this.q) { // not tag(this.q)
            inline else => |u|
                return @field(this, @tagName(u)) == @field(other, @tagName(u)),
        }
    }

```

(`tag()` is just an alias for `std.meta.activeTag()`, it won’t do here)

but anyway, same story:

```zig
4.zig:53:46: error: unable to evaluate comptime expression
                return @field(this, @tagName(u)) == @field(other, @tagName(u)),
                                             ^

```

---

<div class="post-metadata">

**Author:** ![Sze](https://ziggit.dev/user_avatar/ziggit.dev/sze/32/496_2.png) [@Sze](https://ziggit.dev/u/Sze)\
**Post date:** [September 28, 2024, 11:00am UTC](https://ziggit.dev/t/nested-tagged-unions/6173/5 "2024-09-28T11:00:52Z")

</div>

I think you want something more like this:

```zig
switch (this.q) { // not tag(this.q)
    inline else => |u, tag|
        return u == @field(other.q, @tagName(tag)),
}

```

related topic:

> [@Union Equality](https://ziggit.dev/t/union-equality/4060/7):
>
> 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 {…

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 11:08am UTC](https://ziggit.dev/t/nested-tagged-unions/6173/6 "2024-09-28T11:08:28Z")

</div>

> [@Sze](#):
>
> related topic:

Yes, I saw similar patterns here and in some other places around the forum, but have not thought out how to apply them in my case.

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 11:12am UTC](https://ziggit.dev/t/nested-tagged-unions/6173/7 "2024-09-28T11:12:53Z")

</div>

> [@Sze](#):
>
> I think you want something more like this:

ok,

```zig
    fn isEquatableTo(this: Value, other: Value) bool {

        if (tag(this.q) != tag(other.q))
            return false;

        switch (this.q) {
            inline else => |u, t|
                return u == @field(other.q, @tagName(t)),
        }
    }

```

Now compiler says

```plaintext
4.zig:59:26: error: operator == not allowed for type '4.Value.Quantity.DistUnit'
                return u == @field(other.q, @tagName(t)),
                       ~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

```

---

<div class="post-metadata">

**Author:** ![Sze](https://ziggit.dev/user_avatar/ziggit.dev/sze/32/496_2.png) [@Sze](https://ziggit.dev/u/Sze)\
**Post date:** [September 28, 2024, 11:24am UTC](https://ziggit.dev/t/nested-tagged-unions/6173/8 "2024-09-28T11:24:39Z")

</div>

Easiest would be to simplify the union to enums like @IntegratedQuantum suggested, otherwise this works:

```zig
fn isEquatableTo(this: Value, other: Value) bool {
    if (tag(this.q) != tag(other.q))
        return false;

    switch (this.q) {
        inline else => |u, t| return tag(u) == tag(@field(other.q, @tagName(t))),
    }
}

```

It gets confusing with the nested unions and multiple tags.

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 11:33am UTC](https://ziggit.dev/t/nested-tagged-unions/6173/9 "2024-09-28T11:33:45Z")

</div>

> [@Sze](#):
>
> Easiest would be to simplify the union to enums like @IntegratedQuantum suggested

Yes, I’ll try my other model later.

> [@Sze](#):
>
> otherwise this works

Ohhh, thanx a lot!!!  
Will mark as solution.

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 12:13pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/10 "2024-09-28T12:13:54Z")

</div>

> [@IntegratedQuantum](#):
>
> If all your union types are void, why not just use an enum? This should make it a bit easier to deal with.

> [@Sze](#):
>
> Easiest would be to simplify the union to enums like @IntegratedQuantum suggested,

In the very first version I had just two simple enums, one for physical quantities, one for units of measurement. Value had two fields, `q` and `u`, so it was very easy to check for equatability, just `v1.q == v2.q` and `v1.u == v2.u`. But with this plain construction one can easily measure length in kilograms etc; there must be some quantity-unit “affinity”/“compatibility” table or so.

And with these multi-level tagged unions you can’t measure distance in units of mass, it just won’t compile:

```zig
const th = Value{.name = "tree-height", .q = .{.dist_in = .kg}, .v = 2.5};

```

```plaintext
4.zig:72:60: error: no field named 'kg' in enum '@typeInfo(4.Value.Quantity.DistUnit).Union.tag_type.?'
    const b = Value{.name = "tree-height", .q = .{.dist_in = .kg}, .v = 2.5};
                                                  ~~~~~~~~~^~~~~

```

So that was the reason of using nested (multi-level) constructions.

---

<div class="post-metadata">

**Author:** ![IntegratedQuantum](https://ziggit.dev/user_avatar/ziggit.dev/integratedquantum/32/782_2.png) [@IntegratedQuantum](https://ziggit.dev/u/IntegratedQuantum)\
**Post date:** [September 28, 2024, 12:18pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/11 "2024-09-28T12:18:33Z")

</div>

What I was suggesting is to replace the inner-most unions with enums. You’d still use a nested construct with all of its benefits, just your inner level would be an enum instead of a union.

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 12:22pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/12 "2024-09-28T12:22:16Z")

</div>

I see, I just described the “evolution” of my thoughts.

> [@Sze](#):
>
> It gets confusing with the nested unions and multiple tags

Nesting is needed just because Q:U is one-to-many relation.

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 1:34pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/13 "2024-09-28T13:34:41Z")

</div>

> [@Sze](#):
>
> otherwise this works:
> 
> ```zig
> switch (this.q) {
> inline else => |u, t|
> return tag(u) == tag(@field(other.q, @tagName(t))),
> }
> 
> ```

btw, this

```zig
        return switch (this.q) {
            inline else => |u, t|
                tag(u) == tag(@field(other.q, @tagName(t))),
        };

```

seems to work just as well.  
I am a bit in a maze… which one is “better”? 🙂

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 2:05pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/14 "2024-09-28T14:05:50Z")

</div>

> [@IntegratedQuantum](#):
>
> What I was suggesting is to replace the inner-most unions with enums. You’d still use a nested construct with all of its benefits, just your inner level would be an enum instead of a union

Yes, here it is:

```zig
const tag = @import("std").meta.activeTag;
const p = @import("std").debug.print;

const Value = struct {

    const Quantity = enum {
        dist, // short for 'distance'
        time,
        mass,
    };

    const DistUnit = enum {
        mm, m, km,
    };

    const TimeUnit = enum {
        s, min, h,
    };

    const MassUnit = enum {
        mg, g, kg,
    };

    const UnitOf = union(Quantity) {
        dist: DistUnit,
        time: TimeUnit,
        mass: MassUnit,
    };

    nam: []const u8,
    uof: UnitOf,
    val: f32,

    fn quantityName(v: Value) []const u8 {
        return @tagName(v.uof);
    }

    fn unitName(v: Value) []const u8 {
        return switch (v.uof) {
            inline else => |u| @tagName(u),
        };
    }

    fn isEquatableTo(self: Value, other: Value) bool {
        if (tag(self.uof) != tag(other.uof))
            return false;

        return switch (self.uof) {
            inline else => |u, t|
                u == @field(other.uof, @tagName(t)),
        };
    }
};

pub fn main() !void {
    const a = Value {.nam = "melon-1-mass", .uof = .{.mass = .kg}, .val = 3.5};
    const b = Value {.nam = "melon-2-mass", .uof = .{.mass = .g}, .val = 2900.0};
    p("{s} is {} {s}\n", .{a.nam, a.val, a.unitName()});
    p("{s} is {} {s}\n", .{b.nam, b.val, b.unitName()});
    const yn = if (a.isEquatableTo(b)) "yes" else "no";
    p("is {s} comparable to {s}? {s}\n", .{a.nam, b.nam, yn});
}

```

---

<div class="post-metadata">

**Author:** ![dude\_the\_builder](https://ziggit.dev/user_avatar/ziggit.dev/dude_the_builder/32/557_2.png) [@dude\_the\_builder](https://ziggit.dev/u/dude_the_builder)\
**Post date:** [September 28, 2024, 2:26pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/15 "2024-09-28T14:26:02Z")

</div>

I understand you prefer to be explicit and clear in your source code, but note that you can consolidate all those enums into just one tagged union literal type definition:

```zig
    uof: union(enum) {
        dist: enum { mm, m, km },
        time: enum { s, min, h },
        mass: enum { mg, g, kg },
    },

```

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 3:07pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/16 "2024-09-28T15:07:45Z")

</div>

I would keep enum for quantities:

```zig
    const Quantity = enum {
        dist, // short for 'distance'
        time, // you can place your philosophy of what "time" is here :-)
        mass, 
    };

    const UnitOf = union(Quantity) { // better than just union(enum)
        dist: enum {mm, m, km},
        time: enum {s, min, h},
        mass: enum {mg, g, kg},
    };

```

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 8:09pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/17 "2024-09-28T20:09:30Z")

</div>

semicolon makes the difference…

```zig
        switch (self.uof) {
            inline else => |u, t|
                return u == @field(other.uof, @tagName(t)),
        } // no ';' ok

```

```zig
        return switch (self.uof) {
            inline else => |u, t|
                u == @field(other.uof, @tagName(t)),
        }; // without ';' - // error: expected ';' after statement

```

is it intended?

---

<div class="post-metadata">

**Author:** ![dude\_the\_builder](https://ziggit.dev/user_avatar/ziggit.dev/dude_the_builder/32/557_2.png) [@dude\_the\_builder](https://ziggit.dev/u/dude_the_builder)\
**Post date:** [September 28, 2024, 8:41pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/18 "2024-09-28T20:41:25Z")

</div>

I believe the rule of thumb here is that if it produces a value, it requires a semicolon. If it doesn’t produce a value and thus is control-flow only, it doesn’t allow a semicolon. This also happens with `if`, `for`, and `while` which can be control-flow-only (producing no value) and not allowing semicolon, versus their value-producing forms where the semicolon is required.

---

<div class="post-metadata">

**Author:** ![dee0xeed](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/3ab097/32.png) [@dee0xeed](https://ziggit.dev/u/dee0xeed)\
**Post date:** [September 28, 2024, 8:54pm UTC](https://ziggit.dev/t/nested-tagged-unions/6173/19 "2024-09-28T20:54:58Z")

</div>

[there was a topic](https://ziggit.dev/t/a-probably-stupid-terminological-question/2185)  
😑
