Перечисления и объединения
Разбираем enum, union и их безопасную комбинацию — tagged union.
Tagged union — объединение, которое помнит, какой именно вариант сейчас активен; благодаря тегу его можно безопасно разбирать через
switch, не рискуя прочитать не тот тип.
Перечисление (enum) задаёт тип с фиксированным набором именованных значений. Объединение (union) хранит одно из нескольких полей в одной области памяти. По отдельности они полезны, но вместе образуют tagged union — один из самых выразительных инструментов Zig.
Перечисление
const Direction = enum { north, east, south, west };
const d = Direction.north;
const name = switch (d) {
.north => "север",
.east => "восток",
.south => "юг",
.west => "запад",
};
Значения enum пишут с точкой: .north. switch по перечислению обязан покрыть все варианты — если вы добавите пятое направление, компилятор заставит обработать и его. Это страховка от забытых случаев.
Объединение
Простой union хранит ровно одно из полей в общей памяти — как union в C. Но читать «не то» поле опасно. Поэтому в Zig чистые union применяют редко; почти всегда берут их безопасную версию.
Tagged union — безопасный вариантный тип
const std = @import("std");
const Value = union(enum) {
integer: i64,
float: f64,
text: []const u8,
};
pub fn main() void {
const v = Value{ .integer = 42 };
switch (v) {
.integer => |n| std.debug.print("целое: {d}\n", .{n}),
.float => |f| std.debug.print("дробное: {d}\n", .{f}),
.text => |s| std.debug.print("текст: {s}\n", .{s}),
}
}
Вывод:
целое: 42
Запись union(enum) создаёт tagged union: к данным автоматически прикрепляется тег-перечисление, отмечающий активный вариант. switch по нему не только выбирает ветку, но и захватывает данные нужного типа через |n|. Прочитать неактивный вариант невозможно — компилятор и рантайм этого не позволят.
Как работает под капотом
Tagged union в памяти — это тег (маленькое целое перечисления) плюс область, достаточная для самого большого варианта. При switch сначала проверяется тег, и только потом читается соответствующее поле. Это даёт безопасность чистого алгебраического типа из функциональных языков, но без сборщика мусора и с предсказуемой раскладкой памяти.
Частые ошибки
Первая — использовать «голый» union там, где нужен union(enum), и читать неактивное поле — это неопределённое поведение. Вторая — забыть, что switch по enum или tagged union обязан быть исчерпывающим. Третья — путать тег и значение: тег говорит, какой вариант, а захват |x| даёт само значение.
Итог
enum— фиксированный набор именованных значений;switchпо нему исчерпывающий.unionхранит одно из полей в общей памяти, но небезопасен при чтении не того поля.union(enum)— tagged union: тег запоминает активный вариант.switchпо tagged union безопасно разбирает варианты и захватывает данные.