An enum is a type whose value must be one item from a fixed list.
Use an enum when a value has a small number of named choices.
For example, a traffic light can be red, yellow, or green:
const TrafficLight = enum {
red,
yellow,
green,
};This defines a new type named TrafficLight.
A value of this type can only be one of these:
TrafficLight.red
TrafficLight.yellow
TrafficLight.greenIt cannot be some random string or number.
Creating Enum Values
You can create an enum value by writing the enum type, a dot, and the tag name:
const light = TrafficLight.red;Here, light has the value red.
When the expected type is already known, Zig lets you use a shorter form:
const light: TrafficLight = .red;The type annotation tells Zig that .red means TrafficLight.red.
This shorter style is common in Zig.
Why Enums Are Useful
Without enums, you might write code like this:
const status = 2;But what does 2 mean?
Does it mean active? Failed? Pending? Closed?
An enum gives the value a name:
const Status = enum {
pending,
active,
failed,
closed,
};
const status = Status.active;Now the code explains itself.
The compiler also checks that you only use valid values.
Switching on an Enum
Enums work well with switch.
const std = @import("std");
const Status = enum {
pending,
active,
failed,
closed,
};
pub fn main() void {
const status: Status = .active;
switch (status) {
.pending => std.debug.print("pending\n", .{}),
.active => std.debug.print("active\n", .{}),
.failed => std.debug.print("failed\n", .{}),
.closed => std.debug.print("closed\n", .{}),
}
}Output:
activeThe switch handles every possible enum tag.
That is one of the main benefits. If you later add a new enum tag, Zig can force you to update the switch.
Exhaustive Switches
An exhaustive switch handles every possible value.
switch (status) {
.pending => {},
.active => {},
.failed => {},
.closed => {},
}Because Status has exactly four possible values, the switch must cover all four.
This is safer than using loose integers or strings. The compiler knows the full list of possible values.
You can also use else:
switch (status) {
.active => std.debug.print("active\n", .{}),
else => std.debug.print("not active\n", .{}),
}Use else when you really want to group the remaining cases together. For important state machines, explicit cases are usually better.
Enum Methods
Like structs, enums can contain functions.
const Direction = enum {
north,
south,
east,
west,
fn isVertical(self: Direction) bool {
return switch (self) {
.north, .south => true,
.east, .west => false,
};
}
};Use it like this:
const d: Direction = .north;
const vertical = d.isVertical();The method receives self: Direction.
This works the same way as struct methods. The call:
d.isVertical()is shorthand for:
Direction.isVertical(d)A Complete Enum Example
const std = @import("std");
const Direction = enum {
north,
south,
east,
west,
fn opposite(self: Direction) Direction {
return switch (self) {
.north => .south,
.south => .north,
.east => .west,
.west => .east,
};
}
};
pub fn main() void {
const d: Direction = .north;
const other = d.opposite();
std.debug.print("{}\n", .{other});
}This prints the opposite direction.
The important part is this method:
fn opposite(self: Direction) Direction {
return switch (self) {
.north => .south,
.south => .north,
.east => .west,
.west => .east,
};
}It takes one Direction and returns another Direction.
Enum Values Are Not Strings
This enum tag:
.redis not the string "red".
It is a typed enum value.
That means this is wrong:
const color: Color = "red"; // errorYou must use the enum tag:
const color: Color = .red;This distinction matters. Strings are text. Enums are fixed symbolic values known to the compiler.
Enum Values Are Not Plain Integers
Enums may have integer representations internally, but you should not treat them as plain integers in normal code.
This is the right style:
const mode: Mode = .read;This is usually the wrong style:
const mode = 0;The second version loses meaning and type safety.
When you need explicit integer values, Zig supports that too.
Enums with Explicit Integer Values
You can choose the integer tag type:
const HttpStatus = enum(u16) {
ok = 200,
not_found = 404,
internal_server_error = 500,
};Here, the enum uses u16 as its tag type.
Each tag has a numeric value:
ok = 200
not_found = 404
internal_server_error = 500This is useful when the numbers are part of an external format, protocol, ABI, or file format.
Example:
const status: HttpStatus = .not_found;The enum value is still not just a raw u16. It is an HttpStatus.
Converting Enums to Integers
You can convert an enum value to its integer tag with @intFromEnum.
const code = @intFromEnum(HttpStatus.not_found);Now code is 404.
Complete example:
const std = @import("std");
const HttpStatus = enum(u16) {
ok = 200,
not_found = 404,
internal_server_error = 500,
};
pub fn main() void {
const status: HttpStatus = .not_found;
const code = @intFromEnum(status);
std.debug.print("code = {}\n", .{code});
}Output:
code = 404Use this when you need the numeric representation.
Converting Integers to Enums
Converting an integer to an enum is more delicate.
The integer may not match a valid enum tag.
For example, this enum only allows three values:
const Color = enum(u8) {
red = 1,
green = 2,
blue = 3,
};The integer 9 is not a valid Color.
Zig provides tools for this kind of conversion, but you must be careful because not every integer is valid. In beginner code, avoid converting raw integers into enums unless you are parsing external data and validating it carefully.
A safer design is often to parse with a switch:
fn colorFromByte(byte: u8) ?Color {
return switch (byte) {
1 => .red,
2 => .green,
3 => .blue,
else => null,
};
}Now the function returns ?Color.
That means: either a valid Color, or null.
Enum Literals
A value like this is an enum literal:
.redIt does not name the enum type directly.
So Zig needs context.
This works:
const color: Color = .red;The type annotation provides the context.
This also works:
fn paint(color: Color) void {
_ = color;
}
paint(.red);The function parameter tells Zig that .red means Color.red.
But this does not work by itself:
const x = .red; // not enough type informationZig does not know which enum type .red belongs to.
Enums as State
Enums are excellent for representing state.
const ConnectionState = enum {
disconnected,
connecting,
connected,
closing,
};Then code can use a switch:
fn canSend(state: ConnectionState) bool {
return switch (state) {
.disconnected => false,
.connecting => false,
.connected => true,
.closing => false,
};
}This is clearer than using integers:
0 = disconnected
1 = connecting
2 = connected
3 = closingThe enum names carry the meaning.
Enums as Modes
Enums are also good for mode selection.
const OpenMode = enum {
read,
write,
append,
};A function can accept the enum:
fn openFile(path: []const u8, mode: OpenMode) void {
_ = path;
switch (mode) {
.read => {},
.write => {},
.append => {},
}
}The call is clear:
openFile("data.txt", .read);The function signature tells Zig that .read is an OpenMode.
Common Mistake: Using Strings for Fixed Choices
Beginners often reach for strings:
const mode = "read";This is flexible, but too flexible.
The compiler cannot stop you from writing:
const mode = "raed";That typo is just another string.
With an enum:
const mode: OpenMode = .read;A typo becomes a compile error:
const mode: OpenMode = .raed; // errorUse enums when the choices are known ahead of time.
Common Mistake: Adding else Too Early
This works:
switch (state) {
.connected => true,
else => false,
}But it may hide future mistakes.
Suppose you later add a new state:
const ConnectionState = enum {
disconnected,
connecting,
connected,
closing,
reconnecting,
};The else branch will silently handle .reconnecting.
That may or may not be what you want.
For important logic, prefer explicit cases:
switch (state) {
.disconnected => false,
.connecting => false,
.connected => true,
.closing => false,
.reconnecting => false,
}Now the compiler helps you when the enum changes.
The Main Idea
An enum defines a fixed set of named choices.
const Status = enum {
pending,
active,
failed,
closed,
};A value of this type must be one of those choices:
const status: Status = .active;Use enums for states, modes, categories, commands, directions, result kinds, and any case where a value should come from a known list. Enums make invalid states harder to write and valid states easier to read.