igors84
February 20, 2022, 11:44am
1
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