Skip to content

Feature proposal: Enumflagset (zig bit flags type) #4185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ghost opened this issue Jan 15, 2020 · 8 comments
Closed

Feature proposal: Enumflagset (zig bit flags type) #4185

ghost opened this issue Jan 15, 2020 · 8 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@ghost
Copy link

ghost commented Jan 15, 2020

Below is what I imagine the entry in the zig docs would look like if this feature was implemented.

Edit: added "tag type" and "_SPARE field" to the example below.


Enumflagset

Zig provides enumflagset as a safer and more ergonomic way to implement bit flags, as opposed to using plain integers.

An enumflagset behaves as an integer that is configured with a set of default (power of 2) values, that can be combined with set operations only: union, intersection, complement and equality. (respectively, bitwise: or, and, not, equals). These set operations are closed for the enumflagset, except the equals operator which can accept a 0 as an operand.

The complement operation (bitwise not) is restricted to the bits in use, so masking is not necessary.

// specificying an uint as the tagtype is mandatory
const WidgetAnchor = enumflagset(u4){
  LEFT = 1,
  RIGHT = 2,
  TOP = 4,
  BOTTOM = 8,

  // Mandatory field, must be equal to the complement of the bitwise or of all the other members
  _SPARE = 0,
  // If tagtype was u6, compiler would force you to write:
  // _SPARE = 16 + 32,

  // compile error: must be power of 2
  ERR_EXAMPLE_1 = 3,

  // compile error: cannot be zero
  // (only SPARE can be zero if all the bits of the tagtype are used)
  ERR_EXAMPLE_2 = 0,

  // compile error: duplicate of "LEFT"
  ERR_EXAMPLE_3 = 1,
}


const my_anchor : WidgetAnchor = .LEFT | .RIGHT;

const isAnchoredLeft = (my_anchor & .LEFT) != 0;
const isNotAnchoredLeft = (my_anchor & .LEFT) == 0;

// compile error: only bitwise operators (or, and, not) are allowed for enumflags
const my_anchor2 : WidgetAnchor = my_anchor + .LEFT;

// compile error: Both operands of bitwise-or must be WidgetAnchor members. 
const my_anchor3 : WidgetAnchor = my_anchor | 4;
@Vexu
Copy link
Member

Vexu commented Jan 15, 2020

This will probably be rejected for the reasons given in #2115 (comment)

@ghost
Copy link
Author

ghost commented Jan 15, 2020

This will probably be rejected for the reasons given in #2115 (comment)

Thanks for your input. I guess you refer to this part:

Andrew: What I'm not convinced of: Allowing integer operations on extern enums.

This proposal is meant to be orthogonal to "normal" enums. If you want an enum, extern or otherwise, you will use that. If you want bit flags, you might want this proposed feature instead of using non-exhaustive enums or plain unsigned integers for some extra ergonomics, safety and clarity.

The complement operation (bitwise not) is restricted to the bits in use, so masking is not necessary.

Would enforcing something like this in non-exhaustive enums be possible for example?

@Vexu
Copy link
Member

Vexu commented Jan 15, 2020

More importantly I was referring to this:

An enum is a scalar value; flags is a set of booleans.

A better way to do bit flags is to use a packed struct with bools:

const WidgetAnchor = packed struct{
    left: bool,
    right: bool,
    top: bool,
    bottom: bool,
};
@bitSizeOf(WidgetAnchor) == @bitSizeOf(u4)

@travisstaloch
Copy link
Contributor

Thanks. I didn't realize packed bools only require one bit. Guess so 👍 ⚡

test "bit flags" {
    const Bools8 = packed struct {
        a: bool = false,
        b: bool = false,
        c: bool = false,
        d: bool = false,
        e: bool = false,
        f: bool = false,
        g: bool = false,
        h: bool = false,
        const Self = @This();
        pub fn as_u8(self: Self) u8 {
            return @bitCast(u8, self);
        }
    };
    assert(@sizeOf(Bools8) == @sizeOf(u4));
    assert(@sizeOf(Bools8) == @sizeOf(u8));

    var bools = Bools8{};
    bools.b = true;
    var i: u4 = 0;
    const as_u8 = bools.as_u8();
    while (i < 8) : (i += 1) {
        const b = (as_u8 & (@as(u16, 1) << i)) != 0;
        assert(if (i == 1) b else !b);
    }

    inline for (std.meta.fields(Bools8)) |f, j| {
        const b = @field(bools, f.name);
        warn("{}: {}\n", .{ f.name, b });
        assert(if (j == 1) b else !b);
    }
}

@JesseRMeyer
Copy link

Does

    assert(@sizeOf(Bools8) == @sizeOf(u4));
    assert(@sizeOf(Bools8) == @sizeOf(u8));

succeed?

Or was

    assert(@sizeOf(Bools8) == @sizeOf(u4));
    assert(@sizeOf(Bools8.as_u8()) == @sizeOf(u8));

intended?

@travisstaloch
Copy link
Contributor

no, it was intended. try running:

test "sizeof u1, u0" {
    std.debug.warn("sizeof u1:{}\n", .{@sizeOf(u1)});
    std.debug.warn("sizeof u0:{}\n", .{@sizeOf(u0)});
    std.debug.warn("sizeof u8:{}\n", .{@sizeOf(u8)});
}

you'll see that @sizeof(u1) == @sizeof(u8) == 1

@data-man
Copy link
Contributor

There is also bitset-zig.
Adapting to Zig-master is easy.

@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Jan 28, 2020
@andrewrk andrewrk added this to the 0.7.0 milestone Jan 28, 2020
@ghost
Copy link
Author

ghost commented Apr 22, 2020

Closing in favor of #5132 and #5049 .

Syntax in case of #5132

const bitflag = typedef(u2, .Bitflag) {
  const SPARE : u2 = 0;
  const FLAG1 : u2 = 1;
  const FLAG2 : u2  = 2; 
};

@ghost ghost closed this as completed Apr 22, 2020
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

5 participants