Files
ghostty/src/lib/struct.zig
2026-03-14 14:21:00 -07:00

60 lines
2.1 KiB
Zig

const std = @import("std");
const testing = std.testing;
const Target = @import("target.zig").Target;
/// Create a struct type that is C ABI compatible from a Zig struct type.
///
/// When the target is `.zig`, the original struct type is returned as-is.
/// When the target is `.c`, the struct is recreated with an `extern` layout,
/// ensuring a stable, C-compatible memory layout.
///
/// This handles packed structs by resolving zero alignments to the natural
/// alignment of each field's type, since extern structs require explicit
/// alignment. This means packed struct fields like `bool` will take up
/// their full size (1 byte) rather than being bit-packed.
pub fn Struct(
comptime target: Target,
comptime Zig: type,
) type {
return switch (target) {
.zig => Zig,
.c => c: {
const info = @typeInfo(Zig).@"struct";
var fields: [info.fields.len]std.builtin.Type.StructField = undefined;
for (info.fields, 0..) |field, i| {
fields[i] = .{
.name = field.name,
.type = field.type,
.default_value_ptr = field.default_value_ptr,
.is_comptime = field.is_comptime,
.alignment = if (field.alignment > 0) field.alignment else @alignOf(field.type),
};
}
break :c @Type(.{ .@"struct" = .{
.layout = .@"extern",
.fields = &fields,
.decls = &.{},
.is_tuple = info.is_tuple,
} });
},
};
}
test "packed struct converts to extern with full-size bools" {
const Packed = packed struct {
flag1: bool,
flag2: bool,
value: u8,
};
const C = Struct(.c, Packed);
const info = @typeInfo(C).@"struct";
try testing.expectEqual(.@"extern", info.layout);
try testing.expectEqual(@as(usize, 1), @sizeOf(@FieldType(C, "flag1")));
try testing.expectEqual(@as(usize, 1), @sizeOf(@FieldType(C, "flag2")));
try testing.expectEqual(@as(usize, 1), @sizeOf(@FieldType(C, "value")));
try testing.expectEqual(@as(usize, 3), @sizeOf(C));
}