zig-skills/references/std-enums.md

6.8 KiB

std.enums

Utilities for working with enums: sets, maps, arrays, and iteration backed by bit operations.

EnumSet

Bit-backed set of enum values. Zero allocation, copyable by value.

const std = @import("std");

const Color = enum { red, green, blue, yellow };
const ColorSet = std.enums.EnumSet(Color);

// Initialize
var colors: ColorSet = .empty;
const all: ColorSet = .full;

// Struct-style init
var primary = ColorSet.init(.{
    .red = true,
    .green = true,
    .blue = true,
    .yellow = false,
});

// From slice
var some = ColorSet.initMany(&.{ .red, .blue });

// Single element
var just_red = ColorSet.initOne(.red);

EnumSet Operations

// Insert/remove
colors.insert(.red);
colors.remove(.blue);
colors.toggle(.green);
colors.setPresent(.yellow, true);

// Check
if (colors.contains(.red)) {
    // red is in set
}

const n = colors.count();  // number of elements

// Set operations (in-place)
colors.setUnion(other);        // add all from other
colors.setIntersection(other); // keep only common
colors.toggleSet(other);       // XOR
colors.toggleAll();            // invert all

// Set operations (return new set)
const u = colors.unionWith(other);
const i = colors.intersectWith(other);
const x = colors.xorWith(other);
const d = colors.differenceWith(other);  // colors - other
const c = colors.complement();           // all except colors

// Comparison
if (colors.eql(other)) { }
if (colors.subsetOf(other)) { }
if (colors.supersetOf(other)) { }

EnumSet Iteration

var it = colors.iterator();
while (it.next()) |color| {
    std.debug.print("{}\n", .{color});
}

EnumMap

Map from enum to value. Fixed-size, zero allocation.

const Color = enum { red, green, blue };
const ColorMap = std.enums.EnumMap(Color, u32);

// Empty map
var map = ColorMap{};

// Struct-style init (null = not present)
var scores = ColorMap.init(.{
    .red = 100,
    .green = 50,
    .blue = null,  // not in map
});

EnumMap Operations

// Insert
map.put(.red, 42);

// Get
if (map.get(.red)) |value| {
    std.debug.print("red = {}\n", .{value});
}

// Get with default
const value = map.get(.blue) orelse 0;

// Get pointer
if (map.getPtr(.red)) |ptr| {
    ptr.* += 1;  // modify in place
}

// Remove
map.remove(.red);

// Check
if (map.contains(.red)) { }

// Count
const n = map.count();

EnumMap Iteration

// Iterate entries
var it = map.iterator();
while (it.next()) |entry| {
    std.debug.print("{}: {}\n", .{ entry.key, entry.value.* });
}

// Iterate keys only by ignoring each entry's value pointer
var key_it = map.iterator();
while (key_it.next()) |entry| {
    std.debug.print("{}\n", .{entry.key});
}

EnumArray

Dense array indexed by enum. All values always present.

const Color = enum { red, green, blue };
const ColorArray = std.enums.EnumArray(Color, u32);

// Initialize all to same value
var arr = ColorArray.initFill(0);

// Struct-style init (all must be present)
var rgb = ColorArray.init(.{
    .red = 255,
    .green = 128,
    .blue = 64,
});

// Access
const r = rgb.get(.red);     // 255
rgb.set(.green, 200);
rgb.getPtr(.blue).* = 100;

EnumArray Iteration

// By key
for (std.enums.values(Color)) |color| {
    std.debug.print("{}: {}\n", .{ color, rgb.get(color) });
}

// Public sequential access uses the iterator.
var rgb_it = rgb.iterator();
while (rgb_it.next()) |entry| {
    std.debug.print("{}: {}\n", .{ entry.key, entry.value.* });
}

EnumIndexer

Convert between enum values and dense indices.

const Indexer = std.enums.EnumIndexer(Color);

const idx = Indexer.indexOf(.green);   // 1
const color = Indexer.keyForIndex(1);  // .green
const count = Indexer.count;           // 3

Utility Functions

// Get all values as slice
const colors = std.enums.values(Color);  // []const Color

// Safe tag name (works with non-exhaustive)
const name = std.enums.tagName(Color, .red);  // "red" or null

// Safe int-to-enum
const maybe = std.enums.fromInt(Color, 1);  // ?.green

Direct Enum Array (Sparse Enums)

For enums with gaps in values:

const Sparse = enum(u8) { a = 1, b = 5, c = 10 };

// Create array indexed by enum int value
const arr = std.enums.directEnumArray(
    Sparse,
    bool,
    8,  // max_unused_slots (gaps allowed)
    .{ .a = true, .b = false, .c = true },
);
// arr is [11]bool, indexed by @intFromEnum

Complete Example: Permission System

const std = @import("std");

const Permission = enum {
    read,
    write,
    execute,
    admin,
};

const Permissions = std.enums.EnumSet(Permission);

const User = struct {
    name: []const u8,
    perms: Permissions,
};

fn canAccess(user: User, required: Permissions) bool {
    // User must have all required permissions
    return required.subsetOf(user.perms);
}

pub fn main() void {
    const admin = User{
        .name = "admin",
        .perms = .full,
    };

    const reader = User{
        .name = "reader",
        .perms = Permissions.initOne(.read),
    };

    const write_required = Permissions.initMany(&.{ .read, .write });

    std.debug.print("admin can write: {}\n", .{canAccess(admin, write_required)});   // true
    std.debug.print("reader can write: {}\n", .{canAccess(reader, write_required)}); // false
}

Complete Example: State Machine Transitions

const std = @import("std");

const State = enum { idle, running, paused, stopped };
const Event = enum { start, pause, resume, stop };

const TransitionMap = std.enums.EnumMap(Event, State);
const StateTransitions = std.enums.EnumArray(State, TransitionMap);

const transitions = StateTransitions.init(.{
    .idle = TransitionMap.init(.{ .start = .running, .stop = .stopped }),
    .running = TransitionMap.init(.{ .pause = .paused, .stop = .stopped }),
    .paused = TransitionMap.init(.{ .resume = .running, .stop = .stopped }),
    .stopped = TransitionMap{},  // no transitions from stopped
});

fn nextState(current: State, event: Event) ?State {
    return transitions.get(current).get(event);
}

pub fn main() void {
    var state = State.idle;
    state = nextState(state, .start) orelse state;  // -> running
    state = nextState(state, .pause) orelse state;  // -> paused
    state = nextState(state, .resume) orelse state; // -> running
    std.debug.print("Final state: {}\n", .{state});
}

Notes

  • EnumSet: Bit-backed, use for presence tracking
  • EnumMap: Fixed-size dense value storage plus a presence bitset
  • EnumArray: Dense, all values always present
  • All are fixed-size, zero-allocation, copyable by value
  • Use std.StaticBitSet for non-enum integer sets
  • Exhaustive sparse enums are remapped densely. For non-exhaustive enums, index-based containers span the representable tag range rather than only explicit fields, which can make them impractically large; check each container's EnumIndexer behavior before use.