Zig перенёс управление пакетами из компилятора в сборку

Разработчики языка Zig опубликовали выпуск devlog с тремя техническими апдейтами.

Первый, автор, Robbie Lyman: стандартная библиотека распространила механизм «блокировок стабильности указателей» (Pointer Stability Locks) на контейнер ArrayList. Раньше, с 2024 года, эта защита работала только для Hash Map; для ArrayList её реализовали по pull request, который ещё в 2025 году открыл Leo Emar-Kar. Проблема, которую она решает: если код сохранил указатель или срез на элементы ArrayList (например, чтобы парсить данные без лишних копий), а список потом вырос и переехал в памяти при очередной вставке, сохранённый указатель «протухает» и указывает на освобождённую или чужую память, из-за чего рождается баг, который трудно поймать обычными средствами (в примере из devlog это выглядит как тихая подмена содержимого строки на мусор). Теперь перед тем как брать такой указатель или срез, нужно вызвать history.lockPointers(), а после того как он больше не нужен, history.unlockPointers(); если код всё же попытается изменить список (например, вызвать addManyAsSlice), пока указатели заблокированы, программа паникует и печатает стек-трейс с точным местом нарушения, вместо тихого повреждения памяти в рантайме. Отдельно отмечено: поскольку ArrayList, в отличие от Hash Map, упорядочен, операции orderedRemove() и pop() тоже двигают элементы внутри уже выделенной памяти и потому тоже срабатывают на ту же проверку, хотя сами по себе ничего не аллоцируют.

Второй апдейт, автор, Andrew Kelley: всё управление пакетами (подкоманды zig build, zig fetch, zig init, zig libc) переехало из исполняемого файла компилятора в процесс «maker», то есть в саму систему сборки. Вместе с ним туда же переехали загрузка пакетов по сети, HTTP-клиент, TLS и связанная криптография, работа с Git-протоколом, распаковка xz/gzip/zstd/flate/zip и разбор файлов build.zig.zon. Практический эффект: этот код теперь можно патчить и обновлять без пересборки самого компилятора; процесс maker собирается в режиме ReleaseSafe, поэтому у сетевых операций управления пакетами появились дополнительные проверки безопасности; а криптография для сети и хеширования файлов может задействовать редкие процессорные инструкции, которые раньше избегали, чтобы не завязывать на них универсальную сборку компилятора. Заодно бинарник компилятора (сборка без LLVM, в режиме ReleaseSmall) уменьшился на 4%, с 14.1 до 13.5 МиБ. Флаг --maker-optflag заменён переменной окружения ZIG_DEBUG_MAKER, а --zig-lib-dir, переменной ZIG_LIB_DIR. Изначальный мотив переноса, подготовить протокол build-сервера, нужный, чтобы разблокировать работу языкового сервера ZLS: после того как компилятор разделили на процессы configurer и maker, это сломало флаг --build-runner, и Kelley благодарит участника ZLS-команды Techatrix за помощь с протоколом (тот, по словам Kelley, ищет спонсорскую поддержку). Kelley перечисляет оставшиеся блокеры перед тегом Zig 0.17.0: MVP протокола build-сервера, поддержку путевых зависимостей самого build-скрипта, обнаружение изменений build-скрипта в режиме zig build --watch и баг с промахом кеша при смене рабочей директории; сам он предупреждает, что реально сможет вернуться к ним не раньше начала августа, в июле у него две конференции и доклады к ним.

Третий апдейт, автор, Ali Cheraghi: бэкенд компиляции в SPIR-V (используется, в частности, для GPU-шейдеров и вычислительных ядер) частично «прогнил» после недавних изменений в компиляторе, и его привели в порядок за несколько недель работы. Добавлен новый builtin @SpirvType для типов, которых нет в системе типов самого Zig, сэмплеров, изображений, массивов переменной длины и подобного. Информация о режиме выполнения (например, размер рабочей группы или точка привязки фрагментного шейдера) теперь передаётся через calling convention функции, а не через отдельную ассемблерную инструкцию OpExecutionMode: старый хелпер std.gpu.executionMode() убрали, а ассемблер SPIR-V теперь отклоняет ручные инструкции OpExecutionMode. Добавлены две новые calling convention, spirv_task и spirv_mesh, для конвейеров mesh-шейдинга. Возможности и расширения SPIR-V (capabilities и extensions) теперь выводятся автоматически из набора процессорных фич цели сборки по цепочкам зависимостей из SPIRV-Headers, а не расставляются кодогеном вручную; ассемблер также отклоняет ручные инструкции OpCapability и OpExtension. Кодоген бэкенда стал многопоточным: раньше, с самого первого дня, он выполнялся однопоточно внутри потока линковщика, а теперь каждая задача кодогена производит промежуточное представление (Mir) отдельно, как и в остальных self-hosted бэкендах компилятора.

Ключевые факты

  • Стандартная библиотека Zig распространила «блокировки стабильности указателей» на ArrayList (раньше были только у Hash Map с 2024 года); реализацию сделал Robbie Lyman по PR, открытому Leo Emar-Kar в 2025 году
  • lockPointers()/unlockPointers() превращают тихое повреждение памяти при росте ArrayList в громкую панику со стек-трейсом; под ту же проверку попадают и orderedRemove()/pop(), хотя они ничего не аллоцируют
  • Всё управление пакетами (zig build, zig fetch, zig init, zig libc) переехало из компилятора в процесс сборки «maker», который собирается в режиме ReleaseSafe и получил проверки безопасности сети
  • Бинарник компилятора (без LLVM, ReleaseSmall) уменьшился на 4%, с 14.1 до 13.5 МиБ; флаги --maker-optflag и --zig-lib-dir заменены переменными окружения
  • Бэкенд SPIR-V для GPU получил builtin @SpirvType, перенос режимов выполнения в calling convention, новые conventions spirv_task/spirv_mesh для mesh-шейдинга и многопоточный кодоген

Почему это важно

Все три апдейта, про то, как Zig постепенно закрывает разрыв между «языком системного программирования без скрытых затрат» и практической безопасностью/удобством. Блокировки указателей для ArrayList ловят целый класс use-after-realloc багов на этапе тестирования, а не в проде, раньше такой баг мог месяцами прятаться в коде и проявляться как случайное повреждение данных. Перенос управления пакетами из компилятора в систему сборки, архитектурное решение: оно не только уменьшает бинарник компилятора и ускоряет патчи, но и расчищает путь к протоколу build-сервера, без которого нормально не работает языковой сервер ZLS (автодополнение, подсказки типов в редакторе). А доработка SPIR-V-бэкенда напрямую расширяет применимость Zig как языка для написания GPU-шейдеров и вычислительных ядер, раньше часть нужных для этого типов и режимов выполнения просто нельзя было выразить.

Кому это важно

В первую очередь, разработчикам, которые уже пишут или собираются писать на Zig низкоуровневый код с ручным управлением памятью и хранением сырых указателей/срезов на элементы ArrayList (парсеры, буферы, кастомные структуры данных). Во вторую, разработчикам инструментов вокруг Zig, включая авторов ZLS и других build-интеграций, которым нужен предсказуемый протокол build-сервера. В третью, тем, кто пишет шейдеры и compute-ядра для GPU на Zig через SPIR-V-бэкенд, а также разработчикам самого компилятора, которым теперь проще патчить логику пакетного менеджера без пересборки всей цепочки инструментов.

Как это применить

Для ArrayList: перед тем как сохранить указатель или срез на элементы списка (например, const slice = try list.addManyAsSlice(allocator, n) и использование slice дальше), вызвать list.lockPointers(), а когда указатель больше не нужен, list.unlockPointers() (через defer); любая операция, которая могла бы сдвинуть память списка, пока блокировка активна, вызовет панику с указанием места нарушения вместо тихой порчи данных. Для сборки: если код или скрипты полагались на флаги --maker-optflag и --zig-lib-dir, их нужно заменить переменными окружения ZIG_DEBUG_MAKER и ZIG_LIB_DIR соответственно, сам процесс zig build и его подкоманды (fetch, init, libc) при этом работают как раньше. Для GPU-кода: типы вроде сэмплеров и изображений теперь объявляются через @SpirvType(...), а параметры режима выполнения (например, размер рабочей группы) задаются прямо в callconv функции-точки входа (.spirv_kernel, .spirv_task, .spirv_mesh и т.д.) вместо отдельного вызова std.gpu.executionMode(), которого больше нет.

Можно ли доверять

Источник, официальный devlog проекта Zig на ziglang.org, куратор, сама команда языка; каждая из трёх записей подписана конкретным автором (Robbie Lyman, Andrew Kelley, Ali Cheraghi), что для внутренней технической хроники, обычная и надёжная практика. Материал набрал 108 очков и 67 комментариев на Hacker News за 18 часов, то есть тема реально обсуждается сообществом Zig, а не проходит незамеченной. Оговорка: сохранённый для пересказа текст обрывается на середине последнего раздела про многопоточный кодоген SPIR-V-бэкенда, поэтому детали и цифры из хвоста этого раздела в пересказ не попали, они не были доступны на момент подготовки материала.

Риски и подводные камни

Zig ещё не дошёл до версии 0.17.0, Kelley прямо перечисляет незакрытые блокеры (MVP протокола build-сервера, путевые зависимости build-скрипта, обнаружение изменений скрипта в режиме --watch, баг с промахом кеша при смене рабочей директории) и предупреждает, что реально вернётся к ним не раньше начала августа из-за двух конференций в июле, то есть часть описанной инфраструктуры пока незавершена и может измениться. Механизм блокировок указателей ArrayList защищает только от того случая, когда код честно вызвал lockPointers() и попытка нарушения произошла на глазах у рантайма в отладочной/тестовой сборке, он не заменяет дисциплину: забыть вызвать lockPointers() вокруг сохранённого указателя по-прежнему можно, и тогда баг снова станет тихим. Также легко упустить, что orderedRemove() и pop() тоже подпадают под блокировку, хотя интуитивно кажется, что раз они «ничего не аллоцируют», то и указатели не тронут.

«Управление пакетами в Zig теперь получило проверки безопасности при работе с сетью, поскольку процесс maker собирается в режиме ReleaseSafe.»

— Andrew Kelley, автор раздела devlog о переносе управления пакетами из компилятора в систему сборки