Announcing my first Zig library: bitflags

I made a Zig library called bitflags.
I wanted to have a better and easier way to manage bitflags compared to the classic C way. I’m curious what do you think about it.
It can be imported using the Zig package manager. More info about that on the GitHub page.
Here’s an example of how to use it:

const std = @import("std");
const assert = std.debug.assert;
const bitflags = @import("bitflags");

// The `Bitflags` function generates a struct that has all flags and needed padding.
const Flags = bitflags.Bitflags(enum(u8) {
    // The value `a`, at bit position `0`.
    a = 0b00000001,
    // The value `b`, at bit position `1`.
    b = 0b00000010,
    // The value `c`, at bit position `4`.
    c = 0b00010000,
});

pub fn main() !void {
    const flags: Flags = @bitCast(@as(u8, 0b10001));

    // Check which flags are used.
    assert(flags.a and !flags.b and flags.c);

    // You can also check flags like this, but you need to make sure that there are
    // no unspecified bits present in the number or the result won't be correct.
    assert(std.meta.eql(flags, .{ .a = true, .c = true }));

    // If you don't think the input will contain only expected bit flags,
    // you can use `bitflags.zeroUnused` to set all padding fields to 0.
    assert(std.meta.eql(bitflags.zeroUnused(flags), .{ .a = true, .c = true }));
}
2 Likes

The library works by generating a struct that has the same size as the enum that’s passed to the Bitflags function, so it can be converted from integers easily using @bitCast. The struct contains bitflags as bool type fields and padding fields are variable size integers, depending on how much padding you need between 2 bitflags. Heres how the generated struct from the example looks like:

root.Bitflags(main.Flags){ .a: bool, .b: bool, .__unused: u2, .c = bool, .__unused4: u3 }

I wrote the reverse of this once. Will definitely be taking a look.

Hmm, interesting. I’m curious what was your use case for that?

Nice, I have written similar except that the driving enum values are the bit index instead of the mask. (I think both approaches are valid, but if you wanted to compare with this approach then check out std.enums.EnumSet in Zig trunk as I ended up somewhere similar.)

Also related:

Also just to follow up about comparisons to the Zig standard library, in 0.11 EnumIndexer (which EnumSet uses to map fields to bits) does not support non-exhaustive enums, so is not so useful to represent bitfields with padding like your example.

However, in trunk/0.12, it does! Unfortunately there is an issue with EnumSet.init that means this does not fully work, so I’ve raised a PR to fix it here: std.enums: fix EnumSet.init and EnumMap.init for non-exhaustive enums by sjb3d · Pull Request #19309 · ziglang/zig · GitHub.