asibahi
December 13, 2025, 10:26am
1
Hello.
I have the following Rust bitfield
fn value_offset(&self) -> u16 { self.flags & 0x3FFF }
fn has_reset(&self) -> bool { self.flags & 0x2000 != 0 }
fn has_advance(&self) -> bool { self.flags & 0x4000 == 0 }
fn has_push(&self) -> bool { self.flags & 0x8000 != 0 }
/// Yes, the same as `has_push`
fn has_mark(&self) -> bool { self.flags & 0x8000 != 0 }
Any idea how to represent that as a type (packed struct or otherwise) in Zig? The main problem is the has_reset flag overlaps with the value_offset value.
rob
December 13, 2025, 10:40am
2
I have something like this for a packed struct for Dear ImGui as an example:
pub const WindowFlags = packed struct {
no_title_bar: bool = false,
no_resize: bool = false,
no_move: bool = false,
no_scrollbar: bool = false,
no_scroll_with_mouse: bool = false,
no_collapse: bool = false,
always_auto_resize: bool = false,
no_background: bool = false,
no_saved_settings: bool = false,
no_mouse_inputs: bool = false,
menu_bar: bool = false,
horizontal_scrollbar: bool = false,
no_focus_on_appearing: bool = false,
no_bring_to_front_on_focus: bool = false,
always_vertical_scrollbar: bool = false,
always_horizontal_scrollbar: bool = false,
no_nav_inputs: bool = false,
no_nav_focus: bool = false,
unsaved_document: bool = false,
no_docking: bool = false,
__reserved: u12 = 0,
pub fn with(a: @This(), b: @This()) @This() {
return fromInt(a.toInt() | b.toInt());
}
pub fn fromInt(flags: i32) @This() {
return @bitCast(flags);
}
pub fn toInt(self: @This()) i32 {
return @bitCast(self);
}
pub const none: @This() = .{};
pub const no_nav: @This() = .{ .no_nav_inputs = true, .no_nav_focus = true };
pub const no_decoration: @This() = .{ .no_title_bar = true, .no_resize = true, .no_scrollbar = true, .no_collapse = true };
};
So if you just put some const variables on the packet struct you can initialise them as you need. That might work for you?
vulpesx
December 13, 2025, 10:43am
3
For the shared fields, I confirmed the bool is in the correct location:
packed union {
offset: u14,
// struct is needed to put the bool in the correct spot
reset: packed struct { _: u13 = 0, b: bool },
}
1 Like
asibahi
December 13, 2025, 12:40pm
4
I considered something like that but it is a bit of an awkward API compared to what I have now (which is just the same functions)
vulpesx
December 13, 2025, 1:54pm
5
I saw (almost nothing of) your other post and nerdsniped myself into doing this
const Packed = BitFields(16, .{
.offset = .{.type = u14, .offset = 0},
.reset = .{.type = bool, .offset = 13},
.advance = .{.type = bool, .offset = 14},
.push = .{.type = bool, .offset = 15},
.mark = .{.type = bool, .offset = 15},
});
/// like a packed struct but allows overlapping fields
/// field is an anonymous struct of either `.name = .{.type = type, .offset = n}``
pub fn BitFields(comptime bits: u16, comptime fields: anytype) type {
return struct {
bits: Bits,
pub const zero: @This() = .{ .bits = 0};
pub const Bits = @Type(.{ .int = .{ .signedness = .unsigned, .bits = bits}});
pub const Field = std.meta.FieldEnum(@TypeOf(fields));
comptime {
for (@typeInfo(@TypeOf(fields)).@"struct".fields) |field| {
const info = @field(fields, field.name);
if (@bitSizeOf(info.type) + info.offset > bits)
@compileError(std.fmt.comptimePrint(
"field '{s}: {}' bitsize: {} + offset: {} excedes {} bits",
.{field.name, info.type, @bitSizeOf(info.type), info.offset, bits}
));
}
}
pub fn get(b: @This(), comptime field: Field) @field(fields, @tagName(field)).type {
const info = @field(fields, @tagName(field));
const RawBits = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(info.type)}});
const raw: RawBits = @truncate(b.bits >> info.offset);
return @bitCast(raw);
}
pub fn set(b: *@This(), comptime field: Field, value: @field(fields, @tagName(field)).type) void {
const info = @field(fields, @tagName(field));
const RawBits = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(info.type)}});
const raw: RawBits = @bitCast(value);
const mask = @as(Bits, std.math.maxInt(RawBits)) << info.offset;
b.bits &= ~mask; // clear
b.bits |= @as(Bits, raw) << info.offset;
}
};
}
You can get(.name) and set(.name, val)
I tried to have an init where you set all the fields, but that doesn’t work well if they overlap.
2 Likes
gwenzek
December 13, 2025, 1:57pm
6
I would omit "value” from the packed struct and add get/set functions for reading the value.
asibahi
December 13, 2025, 3:02pm
7
you’d init from a backing integer value. or bitCast
vulpesx
December 14, 2025, 12:18am
8
From a backing integer is probably preferred. It has .zero for convenience.