Alignment on nested packed u

I have the following code, which produces an error:

./example.zig:25:10: error: expected type '*Wrap(u32)', found '*align(:0:16) Wrap(u32)'
    try c.item.work();
         ^
./example.zig:25:20: note: referenced here
    try c.item.work();
                   ^
Compiler returned: 1

I’m trying to do some fancy things with generics & packed values implement a network protocol. But the error is pretty strange. It only happens if the Wrap struct has multiple elements and is nested in another struct. Any ideas on how to resolve it, aside from @alignCast, which I’m guessing is not a good idea?

const std = @import("std");

pub fn Wrap(comptime T: type) type {
    return packed struct {
        const Self = @This();

        data: packed union { bytes: [@sizeOf(T)]u8, val: T },
        mac: [8]u8,

        pub fn work(self: *Self) !void {
            var buf: [3 * @sizeOf(T)]u8 = undefined;

            _ = std.base64.standard_encoder.encode(&buf, &self.data.bytes);
        }
    };
}

pub const Env = packed struct {
    tag: u32,
    item: Wrap(u32),
};

pub fn main() anyerror!void {
    var c: Env = undefined;
    try c.item.work();
}

FWIW, using extern instead of packed make it compile properly. I think that the memory layout should be the same in this case. But why do I have this error?

I might be wrong, but IIRC structs are word aligned, so if Env packs item after tag without padding, then it would make sense that Zig reports an incompatible type. Out of curiosity, what happens if you add a u32 padding field right after tag?

Maybe this is the difference between how packed and extern behave in this context.

That said, FYI packed structs are buggy in the stage1 compiler and given how popular they are, it’s possible that it will become a priority once self-hosted lands.

Yes, that appear to be the issue. It looks like we must force the packed struct to start on word boundary.

Any idea what is the meaning inside the align? I tried to look at the docs, but couldn’t find anything relevant. The first part seems to be byte alignment, I’m assuming there is bit offset, but there are three numbers here?

Apparently there can be up to four…

where ZigTypePointer is defined as follows: