Create instance of union(enum) from tag

I have a union that has a few fields that are void types, and I have the tag as an enum. For example:

const MyUnion = union(enum) {
    a: Type1,
    b: Type2,
    c: Type3,
    d: void,
    e: void,
};

const tag = std.meta.Tag(MyUnion).e;
const x = [how???];

enum tags will coerce to their respective tagged union if their type is void, so you can just do this:

const x: MyUnion = tag;
2 Likes

A void type is created with an empty block: {}

An option would be to do:

const x = MyUnion{ .e = {} };
// Or using RLS
const x: MyUnion = .{ .e = {}};

// Or using Justus2308's option
const x: MyUnion = .e;
2 Likes

The tag isn’t comptime-known, e was just an example.

The compiler doesn’t like that the union also has non-void fields, and as such does not allow the coercion.

I know that the tag will only refer to a void though.

https://ziglang.org/documentation/0.15.2/#unionInit + inline else should do the trick

3 Likes

If you want to do this for the general case at runtime, you’ll need a switch statement or something. The compiler can’t tell that you’re only ever going to use fields with a void type so it won’t allow that syntax.

As @kristoff has pointed out inline prongs are the way to go.
You could also do something like this without @unionInit since you know that the field is going to be void:

fn coerce(tag: @typeInfo(MyUnion).@"union".tag_type.?) MyUnion {
    switch (tag) {
        inline .d, .e => |t| return t,
        else => unreachable,
    }
}
1 Like

That sounds great, but unionInit is confusing me.
Using

switch (tag) {
    inline else => |ct_tag| {
         return @unionInit(Token, @tagName(ct_tag), {});
    },
}

Makes the compiler complain that {} should be a []u8, which makes no sense.

If you just inline else all values, @unionInit has to cover every single initialization case, so probably one of your union fields has []u8 and not void as a type. You’ll have to only inline the fields with void types (or do some further comptime magic to filter out the ones with void types if you feel like it’s necessary)

1 Like

Is one of the union variants a []u8? The inline switch will try to set any and all variants to void, which will not work if you have variants that are other types.

You probably want:

var empty = [0]u8{};
const x = switch (tag) {
    // All void ones go here
    .d, .e => @unionInit(Token,  @tagName(tag), {}),
    // For a struct or something
    .a => @unionInit(Token, @tagName(tag), .{}),
    // For a slice of u8,
    .b => @unionInit(Token, @tagName(tag), &empty),
};

// or 
const x = @unionInit(Token, @tagName(tag), switch (tag) {
    .d, .e => {},
    .a => .{},
    .b => &empty,
});

Comptime magic achieved!

switch (tag) {
    inline else => |ct_tag| {
        if (@FieldType(Token, @tagName(ct_tag)) != void) unreachable;
        return @unionInit(Token, @tagName(ct_tag), {});
    },
}
4 Likes