Overlapping fields in a packed struct

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.

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?

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

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)

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

I would omit "value” from the packed struct and add get/set functions for reading the value.

you’d init from a backing integer value. or bitCast

From a backing integer is probably preferred. It has .zero for convenience.