Числовые типы и арифметика
Разбираем числовые типы Zig и его честное отношение к переполнению.
Переполнение целого в Zig по умолчанию — ошибка: в отладочной сборке оно ловится и роняет программу с понятным сообщением, а не молча даёт неверный результат, как в C.
Имена числовых типов в Zig говорят сами за себя. Целые — это буква разрядности (i для знаковых, u для беззнаковых) и число бит: i8, u16, i32, u64. В отличие от C, где размер int зависит от платформы, в Zig i32 — это ровно 32 бита везде. Это убирает целый класс ошибок переносимости.
Целые произвольной разрядности
Zig идёт дальше стандартных размеров: можно объявить целое любой ширины от 1 до 65535 бит, например u3 или i7. Это бесценно для работы с регистрами оборудования и упакованными битовыми полями.
const flag: u1 = 1; // ровно один бит
const color: u3 = 5; // три бита: значения 0..7
const big: u128 = 0; // 128-битное целое
Переполнение ловится, а не игнорируется
Главное отличие от C: знаковое переполнение в C — неопределённое поведение, а беззнаковое молча оборачивается. В Zig обычная арифметика в отладочной сборке проверяет переполнение и аварийно завершает программу с диагностикой. Это превращает скрытый баг в громкую ошибку.
var x: u8 = 250;
x += 10; // 260 не влезает в u8 → паника в отладке:
// "integer overflow"
Когда оборачивание нужно осознанно (например, в хеш-функциях), используют специальные операторы с суффиксом %: +%, -%, *%. Они явно говорят «я хочу оборачивание».
var x: u8 = 250;
x +%= 10; // явное оборачивание: 260 → 4 (260 - 256)
Плавающая точка и приведение
Типы с плавающей точкой — f16, f32, f64, f128. Zig не делает неявных сужающих приведений: смешать i32 и u8 в одном выражении просто так нельзя — нужно явное приведение через @intCast или @as. Это тоже про явность: вы видите каждое преобразование типа.
const a: u8 = 200;
const b: u32 = a; // расширение безопасно, разрешено
const c: u8 = @intCast(b); // сужение — только явно
Как работает под капотом
Проверки переполнения вставляет компилятор только в отладочных и safe-сборках. В режиме ReleaseFast они убираются ради скорости, а поведение становится неопределённым — как в C. Поэтому отлаживать логику нужно в safe-режиме, где переполнение поймается. Операторы +% работают одинаково во всех режимах: оборачивание гарантировано.
Частые ошибки
Самая частая — ждать молчаливого оборачивания как в C и удивляться панике. Если оборачивание нужно — берите +%. Вторая ошибка — попытка неявно сузить тип; компилятор не даст и потребует @intCast. Третья — забыть, что в ReleaseFast защита от переполнения отключена, и полагаться на неё в продакшене.
Итог
- Имена типов задают точную разрядность:
i32— всегда 32 бита. - Можно объявить целое любой ширины, например
u3— полезно для битовых полей. - Переполнение в safe-сборке ловится и роняет программу; осознанное оборачивание — операторы
+%,-%,*%. - Неявных сужающих приведений нет — нужно
@intCast/@as.