Bit Sets and Enum Collections
const std = @import("std");
const expect = std.testing.expect;
test "static bit set" {
// 64 bits, stored inline. No allocator anywhere.
var seen: std.StaticBitSet(64) = .empty;
seen.set(3);
seen.set(40);
try expect(seen.isSet(3));
try expect(!seen.isSet(4));
try expect(seen.count() == 2);
// Iterates set bit indices in ascending order.
var it = seen.iterator(.{});
try expect(it.next().? == 3);
try expect(it.next().? == 40);
try expect(it.next() == null);
}
test "set algebra" {
var a: std.StaticBitSet(16) = .empty;
var b: std.StaticBitSet(16) = .empty;
a.set(1);
a.set(2);
b.set(2);
b.set(3);
const both = a.intersectWith(b);
try expect(both.count() == 1 and both.isSet(2));
const either = a.unionWith(b);
try expect(either.count() == 3);
}
test "ranges" {
var mask: std.StaticBitSet(32) = .empty;
mask.setRangeValue(.{ .start = 8, .end = 16 }, true);
try expect(mask.count() == 8);
try expect(mask.findFirstSet().? == 8);
}
const Perm = enum { read, write, execute };
test "EnumSet: a bit set keyed by an enum" {
var perms = std.EnumSet(Perm).initMany(&.{ .read, .write });
try expect(perms.contains(.read));
try expect(!perms.contains(.execute));
perms.insert(.execute);
perms.remove(.write);
try expect(perms.count() == 2);
}
const Light = enum { red, yellow, green };
test "EnumArray: one value per enum tag, no optionals" {
// Every key exists; initialization must cover all of them.
var durations = std.EnumArray(Light, u32).init(.{
.red = 30,
.yellow = 5,
.green = 25,
});
try expect(durations.get(.red) == 30);
durations.set(.yellow, 4);
try expect(durations.get(.yellow) == 4);
}
test "EnumMap: some keys may be absent" {
var overrides = std.EnumMap(Light, u32).init(.{ .red = 45 });
try expect(overrides.get(.red).? == 45);
try expect(overrides.get(.green) == null);
overrides.put(.green, 20);
try expect(overrides.count() == 2);
}A set that fits in registers
std.StaticBitSet(N) stores membership for integers 0..N as bits, inline,
no allocator. Under the hood it is a single integer when N is small enough
and an array of words otherwise, but the API is the same either way: set,
unset, isSet, count. When the size is only known at run time, reach for
std.DynamicBitSet, which does take an allocator.
Union and intersection are single instructions on the backing words, so
unionWith and intersectWith are cheap. findFirstSet and the ascending
iterator let you walk membership without scanning every index.
Containers keyed by an enum
Three types turn an enum into a container, and the difference is what happens to keys you did not set:
| Type | Holds | Missing key |
|---|---|---|
EnumSet(E) | membership only | not present |
EnumArray(E, V) | a V for every tag | cannot happen: all set at init |
EnumMap(E, V) | a V for some tags | get returns null |
EnumArray is total by construction, so its get returns V, not ?V, and
the compiler forces you to supply every field at initialization. Use it for
lookup tables that must cover the whole enum, like a duration per traffic
light state. EnumMap is the partial version for when absence is meaningful.
All three are backed by the same bit-set machinery, so they are compact and
allocation-free.