Compile error when trying to take address of the @field

I wrote this function to byteSwap struct fields recursively but I get a compile error mentioned in the comment bellow. You can paste this code into godbolt.org to try it. Can someone tell me what am I doing wrong and how can I fix it?

const std = @import("std");

fn byteSwapStruct(comptime T: type, value: *T) void {
    inline for (std.meta.fields(T)) |field| {
        switch (@typeInfo(field.field_type)) {
            .ComptimeInt, .Int => {
                @field(value.*, field.name) = @byteSwap(field.field_type, @field(value.*, field.name));
            },
            .Enum => {
                var fieldVal = @enumToInt(@field(value.*, field.name));
                fieldVal = @byteSwap(@TypeOf(fieldVal), fieldVal);
                @field(value.*, field.name) = @intToEnum(field.field_type, fieldVal);
            },
            .Struct => {
                // error: expected type '*TestSubStruct', found '*align(:0:8) TestSubStruct'
                byteSwapStruct(field.field_type, &@field(value.*, field.name));
            },
            else => {
                @compileError(std.fmt.comptimePrint("Type {} in readStructForeign not supported", .{@typeName(field.field_type)}));
            },
        }
    }
}

export fn run() u32 {
    const TestSubStruct = packed struct {
        int32f: u32,
        int8f: u8,
    };
    const TestStruct = packed struct {
        int32f: u32,
        int8f: u8,
        sub: TestSubStruct
    };
    var tstruct = TestStruct { .int32f = 0x10203040, .int8f = 0x50, .sub = .{.int32f = 0x60708090, .int8f = 0xA0}};
    byteSwapStruct(TestStruct, &tstruct);
    return tstruct.int32f;
}
1 Like

I don’t really know why the error happens, and it would be nice if someone could explain the reason, but this version, using anytype seems to work:

fn byteSwapStruct(value: anytype) void {
    inline for (std.meta.fields(@TypeOf(value.*))) |field| {
        switch (@typeInfo(field.field_type)) {
            .ComptimeInt, .Int => {
                @field(value.*, field.name) = @byteSwap(field.field_type, @field(value.*, field.name));
            },
            .Enum => {
                var fieldVal = @enumToInt(@field(value.*, field.name));
                fieldVal = @byteSwap(@TypeOf(fieldVal), fieldVal);
                @field(value.*, field.name) = @intToEnum(field.field_type, fieldVal);
            },
            .Struct => {
                byteSwapStruct(&@field(value.*, field.name));
            },
            else => {
                @compileError(std.fmt.comptimePrint("Type {} in readStructForeign not supported", .{@typeName(field.field_type)}));
            },
        }
    }
}

It would also be nice to have some expert advice on when it’s better to use the comptime T: type versus the anytype ... @TypeOf idioms in Zig.

2 Likes

The problem is the alignment:

./foo.zig:16:90: error: expected type '*TestSubStruct', found '*align(:0:8) TestSubStruct'

Since you’re byte-swapping a packed struct, when you take the address of one of the fields the compiler knows it’s not going to be aligned, but byteSwapStruct says it takes an aligned pointer *T. There’s a number of ways to address this, here’s one way:

fn byteSwapStruct(comptime T: type, value: T) void {
    const Child = switch (@typeInfo(T)) {
        // TODO: handle Optional pointer
        .Pointer => |info| info.child,
        else => @compileError("byteSwapStruct requires a pointer type, but got " ++ @typeName(T)),
    };
    inline for (std.meta.fields(Child)) |field| {
        switch (@typeInfo(field.field_type)) {
            .ComptimeInt, .Int => {
                @field(value.*, field.name) = @byteSwap(field.field_type, @field(value.*, field.name));
            },
            .Enum => {
                var fieldVal = @enumToInt(@field(value.*, field.name));
                fieldVal = @byteSwap(@TypeOf(fieldVal), fieldVal);
                @field(value.*, field.name) = @intToEnum(field.field_type, fieldVal);
            },
            .Struct => {
                // error: expected type '*TestSubStruct', found '*align(:0:8) TestSubStruct'
                byteSwapStruct(@TypeOf(&@field(value, field.name)), &@field(value, field.name));
            },
            else => {
                @compileError(std.fmt.comptimePrint("Type {} in readStructForeign not supported", .{@typeName(field.field_type)}));
            },
        }
    }
}

export fn run() u32 {
    const TestSubStruct = packed struct {
        int32f: u32,
        int8f: u8,
    };
    const TestStruct = packed struct {
        int32f: u32,
        int8f: u8,
        sub: TestSubStruct
    };
    var tstruct = TestStruct { .int32f = 0x10203040, .int8f = 0x50, .sub = .{.int32f = 0x60708090, .int8f = 0xA0}};
    byteSwapStruct(*TestStruct, &tstruct);
    return tstruct.int32f;
}
6 Likes