Опциональные типы вместо null
Учимся выражать «значения может не быть» через ?T и избегать разыменования null.
Опциональный тип
?T— это либо значение типаT, либо специальноеnull; компилятор не даст использовать значение, пока вы явно не проверили, что оно есть.
«Ошибка на миллиард долларов» — так Тони Хоар назвал изобретение null. В C любой указатель может оказаться NULL, и разыменование роняет программу. Zig устраняет это: обычный указатель *T никогда не бывает null. Если значение может отсутствовать, тип явно помечается вопросом: ?*T или ?i32.
Объявление опционального значения
var maybe: ?i32 = 42;
maybe = null; // допустимо: тип опциональный
var required: i32 = 42;
// required = null; // ОШИБКА компиляции: i32 не опционален
Тип ?i32 вмещает либо целое, либо null. А обычный i32 присвоить null нельзя — компилятор запрещает. Это и есть безопасность: невозможность null там, где его не ждут, гарантирована типами.
Распаковка через if-захват
const std = @import("std");
pub fn main() void {
const maybe: ?i32 = 100;
if (maybe) |value| {
std.debug.print("есть значение: {d}\n", .{value});
} else {
std.debug.print("значения нет\n", .{});
}
}
Вывод:
есть значение: 100
Конструкция if (maybe) |value| проверяет, что значение есть, и «захватывает» его в переменную value уже без вопроса — тип i32. Внутри блока вы работаете с гарантированно существующим значением. Если бы было null, выполнилась бы ветка else.
Оператор orelse
const maybe: ?i32 = null;
const value = maybe orelse 0; // если null — берём 0
// value == 0
Оператор orelse подставляет значение по умолчанию, когда опционал пуст. Это компактная альтернатива if/else. А если вы абсолютно уверены, что значение есть, его можно «насильно» распаковать через maybe.? — но при null это паника, поэтому используют осторожно.
Как работает под капотом
Для опционального указателя Zig применяет умную оптимизацию: ?*T занимает столько же байт, сколько обычный указатель, и значение null кодируется нулевым адресом. То есть безопасность достаётся бесплатно, без расходов памяти. Для значений вроде ?i32 добавляется один байт-флаг «есть/нет».
Частые ошибки
Главная — привычка из C считать любой указатель потенциально нулевым. В Zig это не так: если хотите «может быть null», объявляйте ?*T явно. Вторая ошибка — злоупотреблять .? для распаковки: при null это аварийное завершение. Безопаснее if-захват или orelse.
Итог
- Обычный указатель
*Tникогда не бывает null — это гарантия типа. ?Tявно выражает «значение может отсутствовать».- Распаковка:
if (x) |v| { ... }илиx orelse default. ?*Tзанимает столько же памяти, сколько обычный указатель.