Assert than an enum uses all possible values of the backing integer?

I’m de-serializing an enum from a byte stream. In my use case, I want to be able to handle all possble values found in the byte stream.

Normally I would use a non-exhaustive enum (to force all switch statements on the enum to have an else and to prevent a panic on de-serialization) but in this special case I have an enum that represents all the integers in the backing integer.

How can I assert this?

pub const SegmentDataSize = enum(u3) {
    seven_octets = 0x00,
    six_octets = 0x01,
    five_octets = 0x02,
    four_octets = 0x03,
    three_octets = 0x04,
    two_octets = 0x05,
    one_octet = 0x06,
    zero_octets = 0x07,
};

comptime {
    assert(.... all values of the u3 are represented)
}

The comptime assertion ensuring every value of an unsigned backing integer is present in order:

comptime {
    const tag_type = @typeInfo(SegmentDataSize).Enum.tag_type;
    std.debug.assert(@typeInfo(tag_type).Int.signedness == .unsigned);
    for (std.enums.values(SegmentDataSize), 0..std.math.maxInt(tag_type) + 1) |val, int| {
        std.debug.assert(@intFromEnum(val) == int);
    }
}

Also, if you want, it’s possible to remove enum’s ordinal values in this case since by default they start at zero and increase by one, see langref.

Sidenote: in the case of switching on non-exhaustive enums you can use _ instead of else as a switch prong to ensure that every known value is handled, see langref.

6 Likes

TIL, thanks @tensorush!

1 Like

Enum tags can’t have duplicate integer value so you don’t need loop. You can just check that number of fields is equal to 2^(number of bits in backing integer type).
It’s easier, works with signed integers and probably faster to compile.

pub const SegmentDataSize = enum(u3) {
    seven_octets = 0x00,
    six_octets = 0x01,
    five_octets = 0x02,
    four_octets = 0x03,
    three_octets = 0x04,
    two_octets = 0x05,
    one_octet = 0x06,
    zero_octets = 0x07,
};

comptime {
    const enum_info = @typeInfo(SegmentDataSize).Enum;
    std.debug.assert(enum_info.fields.len ==
        1 << @typeInfo(enum_info.tag_type).Int.bits);

}
3 Likes

Yes, that’s a viable option when you don’t specify enum’s ordinal values. But if you do, it’s better to loop through values asserting that there’re no typos. For the same reason it’s better to assert that the backing integer is unsigned (letters ‘u’ and ‘i’ are dangerously close on keyboards). Btw, don’t worry about compilation speed in such trivial cases, incremental is not far off :wink:

1 Like

Why would specifying enum’s ordinal values change anything? Compiler prevents duplicate field names and duplicate tag values already.

The possible typo can be swapped ordinal values, like:

pub const SegmentDataSize = enum(u3) {
    seven_octets = 0x00,
    six_octets = 0x01,
    five_octets = 0x02,
    four_octets = 0x03,
    three_octets = 0x05,
    two_octets = 0x04,
    one_octet = 0x06,
    zero_octets = 0x07,
};

Oh yea, this kind of bugs would be annoying to debug and is pretty easy to make.

2 Likes