Zig Enums

An enum (enumeration) defines a type that can hold exactly one value from a fixed set of named options. Instead of using raw numbers or strings to represent states like "pending," "active," and "closed," you define an enum with those names. The compiler then knows every valid value, catches typos, and forces you to handle every case in a switch.

Defining an Enum

const Direction = enum {
    North,
    South,
    East,
    West,
};
  Direction can only be:
  ┌─────────┐
  │  North  │
  │  South  │  ← Pick exactly one
  │  East   │
  │  West   │
  └─────────┘

Using an Enum

const heading = Direction.North;

switch (heading) {
    .North => std.debug.print("Going up on the map\n", .{}),
    .South => std.debug.print("Going down on the map\n", .{}),
    .East  => std.debug.print("Going right on the map\n", .{}),
    .West  => std.debug.print("Going left on the map\n", .{}),
}

Inside a switch that already knows the type, you use the short dot syntax .North instead of writing the full Direction.North. The compiler fills in the type automatically.

Enum Integer Values

Every enum variant maps to an integer internally. By default Zig assigns 0, 1, 2, ... You can inspect or set these values explicitly:

const StatusCode = enum(u16) {
    Ok          = 200,
    NotFound    = 404,
    ServerError = 500,
};

const code = StatusCode.NotFound;
const num  = @intFromEnum(code);
std.debug.print("Code: {d}\n", .{num});  // 404
  StatusCode:
  .Ok          → 200
  .NotFound    → 404
  .ServerError → 500

  @intFromEnum(.NotFound) = 404
  @enumFromInt(200) = .Ok

The backing integer type (u16) sets how large the number can be. Use the smallest type that fits your largest value.

Comparing Enum Values

const a = Direction.East;
const b = Direction.East;
const c = Direction.West;

std.debug.print("{}\n", .{a == b});  // true
std.debug.print("{}\n", .{a == c});  // false

Enums compare with == and !=. Each variant is a unique value — no two different variants are ever equal.

Enum Methods

Like structs, enums can have methods:

const Season = enum {
    Spring,
    Summer,
    Autumn,
    Winter,

    fn isWarm(self: Season) bool {
        return switch (self) {
            .Spring, .Summer => true,
            .Autumn, .Winter => false,
        };
    }

    fn months(self: Season) []const u8 {
        return switch (self) {
            .Spring => "Mar–May",
            .Summer => "Jun–Aug",
            .Autumn => "Sep–Nov",
            .Winter => "Dec–Feb",
        };
    }
};

const now = Season.Summer;
std.debug.print("Warm: {}\n",   .{now.isWarm()});  // true
std.debug.print("Months: {s}\n", .{now.months()});  // Jun–Aug

Non-Exhaustive Enums

If you are wrapping a value from C or a protocol where new values might appear, mark the enum as non-exhaustive with _:

const KeyCode = enum(u32) {
    Enter     = 13,
    Escape    = 27,
    Space     = 32,
    _,  // allows values not listed above
};

A switch on a non-exhaustive enum requires an else branch for unrecognized values. Without it, the compiler reports an error.

Enum in Structs

const Priority = enum { Low, Medium, High, Critical };

const Task = struct {
    title: []const u8,
    priority: Priority,
    done: bool = false,
};

const tasks = [_]Task{
    .{ .title = "Fix login bug", .priority = .Critical },
    .{ .title = "Update readme",  .priority = .Low },
    .{ .title = "Write tests",    .priority = .High },
};

for (tasks) |task| {
    std.debug.print("[{s}] {s}\n", .{
        @tagName(task.priority),
        task.title
    });
}
  Output:
  [Critical] Fix login bug
  [Low] Update readme
  [High] Write tests

@tagName converts an enum variant to its string name. This is useful for printing and logging enum values.

Iterating Over All Enum Values

const std = @import("std");

const Color = enum { Red, Green, Blue, Yellow };

pub fn main() void {
    inline for (@typeInfo(Color).Enum.fields) |field| {
        std.debug.print("Color: {s}\n", .{field.name});
    }
}

Output:

Color: Red
Color: Green
Color: Blue
Color: Yellow

@typeInfo returns compile-time information about a type. For enums, this includes the list of all fields. The inline for unrolls the loop at compile time, visiting each field as a compile-time constant.

Enum vs Constants

  Constants:                       Enum:
  +--------------------------+     +----------------------------+
  | const NORTH = 0;         |     | const Direction = enum {   |
  | const SOUTH = 1;         |     |     North, South,          |
  | const EAST  = 2;         |     |     East, West,            |
  | const WEST  = 3;         |     | };                         |
  |                          |     |                            |
  | var dir: u8 = NORTH;     |     | var dir = Direction.North; |
  | dir = 99;  ← allowed!    |     | dir = Direction.North;     |
  |            (silent bug)  |     | // dir = 99 → COMPILE ERR  |
  +--------------------------+     +----------------------------+

Constants let any integer value slip through. An enum restricts the value to exactly the defined variants. This restriction is a feature — it catches invalid states at compile time instead of causing mysterious bugs at runtime.

Leave a Comment

Your email address will not be published. Required fields are marked *