Create an union instance with the proper field in a switch

I am trying to optimize the size of my bytecode, and decided to handle compression with these two methods:

    pub fn asBytes(self: OpCode) struct { bytes: [3]u8, len: usize } {
        return switch (self) {
            .Return, .PushNil, .PushTrue, .PushFalse, .Add, .Negate, .Not, .Minus, .Multiply, .Divide, .Equal, .Greater, .Less, .Pop, .PopPreserve, .Inherit => .{
                .bytes = .{ @intFromEnum(self), 0, 0 },
                .len = 1,
            },
            .PushConstant, .DefineGlobal, .GetGlobal, .SetGlobal, .GetLocal, .SetLocal, .GetProperty, .SetProperty, .JumpAlways, .JumpIfFalse, .PopJumpIfFalse, .LoopAlways, .Call, .NewClass, .AddMethod, .AddField => |data| .{
                .bytes = .{ @intFromEnum(self), data, 0 },
                .len = 2,
            },
            .Invoke => |invocation| .{
                .bytes = .{ @intFromEnum(self), invocation.name, invocation.numArg },
                .len = 3,
            },
        };
    }
    pub fn read(bytes: []const u8, index: usize) struct { opcode: OpCode, len: usize } {
        const typ: OpCodeType = @enumFromInt(bytes[index]);
        return switch (typ) {
            .Return, .PushNil, .PushTrue, .PushFalse, .Add, .Negate, .Not, .Minus, .Multiply, .Divide, .Equal, .Greater, .Less, .Pop, .PopPreserve, .Inherit => .{
                .opcode = OpCode{ .typ = void{} },
                .len = 1,
            },
            .PushConstant, .DefineGlobal, .GetGlobal, .SetGlobal, .GetLocal, .SetLocal, .GetProperty, .SetProperty, .JumpAlways, .JumpIfFalse, .PopJumpIfFalse, .LoopAlways, .Call, .NewClass, .AddMethod, .AddField => .{
                .opcode = OpCode{ .typ = bytes[index + 1] },
                .len = 2,
            },
            .Invoke => .{
                .opcode = OpCode{ .typ = .{ .name = bytes[index + 1], .numArg = bytes[index + 2] } },
                .len = 3,
            },
        };
    }

But read() does not compile right now, because typ is not a member of the enum. How can I get it to compile ? Am I forced to have a case for each instruction ?

Here is the precise error message:

OpCode.zig:180:36: error: no field named 'typ' in union 'OpCode.OpCode'
                .opcode = OpCode{ .typ = .{ .name = bytes[index + 1], .numArg = bytes[index + 2] } },

How specifically does it “not compile”? I assume you get a compile error that has a clue as to what the problem is.

1 Like

You don’t use the |data| capture in the switch statement in read. The compile error should say something about an unused capture.

@milogreg @pachde I updated the post with my latest attempt, and with the error message I get.

no field named 'typ' in union 'OpCode.OpCode'

Does your union have this field?

No, it’s supposed to mean ‘use the value of typ to determine the field’. Basically creating .Return if typ == .Return; .Pop if typ == .Pop; etc…
I suppose zig does not like it though, is there a way to do this differently ?

You want @unionInit. The argument to it must be comptime known. In order for this to happen, you need to make the switch prongs inline.

1 Like

Ok, so if I am understanding you right, you want read to take some bytes and an opcode type, then return an opcode of the given type whose value is constructed using the bytes.

By writing .typ I believe that you are intending to access the union field associated with typ’s value. This will not work, as the . syntax will simply attempt to access a field with the name after the dot (typ), which doesn’t exist in your union.

As @LucasSantos91 said, @unionInit can solve this:

inline .PushConstant, .DefineGlobal, .GetGlobal, .SetGlobal, .GetLocal, .SetLocal, .GetProperty, .SetProperty, .JumpAlways, .JumpIfFalse, .PopJumpIfFalse, .LoopAlways, .Call, .NewClass, .AddMethod, .AddField => |tag| .{
    .opcode = @unionInit(OpCode, @tagName(tag), bytes[index + 1]),
    .len = 2,
},
2 Likes

Haha silly me learned how to use vscode snippets in the meantime. Thx everyone, zig rocks !!