Вышел Zig 0.16 — DI в main, I/O как интерфейс и async без раскраски функций
Zig 0.16 меняет точку входа: main получает DI-параметр с аллокатором и I/O, а async работает без раскраски функций. Разбираем, что сломается в коде с 0.15 и ради чего.
Новости TprogerZig 0.16 закрыл проблему раскраски функций: sync и async теперь имеют одинаковую сигнатуру, отличается только переданный I/O-интерфейс. Если вы писали асинхронный Rust и упирались в async fn-заражение — посмотрите, как это решено здесь. 14 апреля 2026 года команда Zig выпустила 0.16.0 после 8 месяцев работы 244 контрибьюторов. Главные изменения: I/O как интерфейс, DI в точке входа («Juicy Main»), новый ELF-линкер и окружение, перестающее быть глобальным.
Zig — системный язык с С-подобным синтаксисом и упором на явное управление памятью. 0.16.0 — не LTS, но в терминах API это один из самых ломающих релизов: многие библиотеки придётся переписывать под новый std.Io. Разбираем, что именно изменилось и ради чего.
Ключевые выводы
Что: Zig 0.16.0 — крупный релиз за 8 месяцев. I/O как интерфейс, DI в main, новый ELF-линкер.
Когда: 14 апреля 2026 года.
Где: ziglang.org/download, тарболы под все Tier-1 и Tier-2 платформы.
Масштаб: 244 контрибьютора, 1183 коммита, ломающие изменения в стандартной библиотеке.
Ключевое для кода: pub fn main(init: std.process.Init) — точка входа получает аллокаторы, Io, env и preopens.
Juicy Main — DI на уровне точки входа
«Juicy Main» — это внутреннее название proposal Эндрю Келли: идея в том, что точка входа должна получать «сочный» набор готовых зависимостей, а не создавать их внутри. DI здесь значит, что аллокатор, I/O, env и preopens передаются в main снаружи, а не конструируются каждым приложением заново. Раньше каждая программа на Zig начиналась с одного и того же бойлерплейта: создать GeneralPurposeAllocator, получить arg-итератор через std.process.argsAlloc, отдельно дёрнуть std.process.getEnvMap. В 0.16 всё это подаётся параметром в main:
std.process.Init — структура с пре-инициализированными значениями: процессная arena с автоматической очисткой при выходе, дефолтный GPA (General Purpose Allocator) с leak-checker в debug, подходящая под таргет реализация Io, карта переменных окружения, preopens (наследие WASI-модели: файловые дескрипторы, переданные родительским процессом как capability). Это классический DI: main собирает зависимости, библиотеки внутри принимают Allocator и Io через параметры.
Подпись main теперь может быть одной из трёх:
- Без параметров — нельзя получить CLI-аргументы и окружение. Подходит для чистых вычислительных утилит.
process.Init.Minimal— только сырыеargvиenviron, без аллокаторов и Io.process.Init— полный набор: аллокаторы, Io, env-map, preopens.
I/O как интерфейс — async без раскраски функций
Второе крупное изменение: std.Io стал абстрактным интерфейсом, а не набором конкретных функций. Реализации:
- Io.Threaded — текущая дефолтная, работает поверх ОС-потоков, поддерживает cancelation.
- Io.Evented — экспериментальная, на userspace-стеках с work-stealing (M:N, «зелёные потоки»).
- Io.Uring — proof-of-concept на Linux io_uring, без сети и обработки ошибок.
- Io.Kqueue — proof-of-concept на macOS/BSD.
- Io.Dispatch — поверх Grand Central Dispatch (macOS).
- Io.failing — возвращает ошибку на любую операцию, для тестов.
Философия — ровно та же, что у Allocator: функции принимают интерфейс, а не привязываются к конкретной реализации. Это даёт Zig async без раскраски функций — то, на чём подрывались Rust, JavaScript и C#. Асинхронная функция ничем не отличается по сигнатуре от синхронной — меняется только переданный Io.
На уровне API появились новые абстракции:
Future(T)— таск, создаётся черезio.async, ожидается черезfuture.await. Может быть реализован синхронно, если Io не умеет concurrency.Group— набор независимых тасков с общим awaiting и cancelation.Queue(T)— многопроизводительная/многопотребительная очередь с блокировкой.SelectиBatch— выполнение нескольких операций одновременно с ожиданием первого завершения.Clock,Duration,Timestamp,Timeout— type-safe единицы времени.
Есть цена: весь код стандартной библиотеки, который делал I/O, пришлось переписать под интерфейс. Старые типы GenericReader, AnyReader, FixedBufferStream удалены — в крейтах, обновлявшихся из 0.15, почти гарантированно сломается чтение/запись.
Environment перестаёт быть глобальным
В C setenv в многопоточной программе — UB: глобальная environ читается без блокировок. Zig до 0.16 наследовал эту проблему через std.os.environ, который ещё и нельзя было заполнить без линковки libc.
Теперь окружение доступно только из main через init.environ_map. Если библиотеке нужен env — она принимает его параметром, как аллокатор. Это ломает код, дёргавший std.process.getEnvVarOwned из произвольного места, но убирает целый класс thread-safety-багов.
Параллельно переименованы функции в std.mem: indexOf → find, добавлены cut / cutScalar для разбиения слайсов по первому/последнему вхождению.
Новый ELF-линкер — без LLVM для Linux
В 0.16 появился собственный ELF-линкер — включается флагом -fnew-linker, а при -fincremental на self-hosted ELF-сборке используется автоматически. Он пока не feature-complete (например, не пишет DWARF), поэтому по умолчанию release-сборки идут через LLVM + LLD. Выгода — инкрементальная линковка (194мс → 65мс на тестовом проекте) и меньше зависимостей для debug-сборок.
Плюс доработки под Windows: сетевой стек теперь работает без ws2_32.dll (напрямую через NtDll), завершена миграция с Win32 API на NtDll для остальных системных вызовов, появился inter-process progress reporting для параллельных сборок.
Что ещё важного в 0.16
- x86-бэкенд компилятора стал самодостаточным — дебажные сборки на x86_64 собираются без LLVM и идут значительно быстрее. aarch64-бэкенд ещё work-in-progress: в 0.16 он падает на behavior-тестах.
- Инкрементальная компиляция переработана, меньше false-rebuilds, стабильнее на больших проектах.
- Fuzzer (
zig test --fuzz) получил multi-process режим, infinite mode и crash dumps с AST-дампом. - Build-система: локальный override пакетов,
--error-styleи--multiline-errors, таймауты юнит-тестов, temporary files API. - Крипто: добавлены AES-SIV, AES-GCM-SIV, Ascon-AEAD, Ascon-Hash, Ascon-CHash.
- Heap:
ArenaAllocatorстал thread-safe и lock-free, обёрткаThreadSafeAllocatorудалена. - Тулчейн: LLVM 21 (с отключённой loop vectorization из-за регрессии), musl 1.2.5, glibc 2.43, Linux 6.19 headers, macOS 26.4 headers, MinGW-w64, FreeBSD 15.0 libc.
Как мигрировать с 0.15
- Ставьте 0.16 из тарбола или zigup параллельно со старой версией. Одновременная установка нескольких версий — штатный сценарий.
- Соберите проект и смотрите на ошибки. Чаще всего сломается: вызовы
std.os.environ,std.process.getEnvVarOwned, использованиеGenericReader/AnyReader,FixedBufferStream. - Перепишите
mainна новую сигнатуру. Даже если просто хотите старое поведение — объявитеpub fn main(init: std.process.Init.Minimal), так вы получите хотя бы args/environ. - Если нужен Io в библиотеке, но вы не контролируете
main— временно используйтеIo.Threadedв режиме single-threaded и беритеthreaded.io(). Это не рекомендуемый путь, но он работает какpage_allocatorдля аллокатора. - Проверьте зависимости через
zig fetch. Старые версии пакетов под 0.15 скорее всего не соберутся, ждите апдейтов или фиксируйте у себя fork. - Пересоберите и прогоните тесты на
std.testing.io— это Io-бэкенд для тестов, аналогstd.testing.allocator.
Часто задаваемые вопросы
Готов ли Zig для production?
Zig ещё до 1.0, LTS не объявлен. При этом на Zig написан Bun (JavaScript-рантайм, сотни тысяч пользователей) и TigerBeetle (финансовая БД). Для системного кода с высокими требованиями к производительности и предсказуемости — Zig используется в продакшене, но готовьтесь к ломающим изменениям API в каждом мажоре.
Сломается ли мой код с 0.15?
С высокой вероятностью — да. Удалены старые Reader/Writer-типы, изменена сигнатура main, убран глобальный environ. Типичная миграция — несколько часов на средний проект.
Что с производительностью новых бэкендов?
Дебажные сборки на x86_64 без LLVM идут значительно быстрее. Качество машкода хуже, чем у LLVM, поэтому для release-сборок LLVM остаётся дефолтом. aarch64-бэкенд self-hosted ещё не готов — используйте LLVM.
Когда Io.Uring станет дефолтом на Linux?
Команда пишет «не в этом релизе». На 0.16 это proof-of-concept без сети и полного покрытия ошибок. Работа идёт, но сроков нет.
Где взять примеры нового стиля?
В самих release notes и в исходниках stdlib. Большинство модулей stdlib переписаны под новый Io — это живой учебник.
Что дальше
После 0.16 любая Zig-функция, принимающаяIo, автоматически работает и в sync-, и в async-режиме без смены сигнатуры. В Rust это требуетasync fn+ executor-specific runtime — в Zig теперь параметр.
0.16.0 — это не про новые фичи, а про фундамент: зафиксированы I/O-интерфейс и контракт точки входа. Если у вас есть Zig-код на 0.15 — закладывайте день на миграцию по чек-листу выше. Если смотрели на Zig со стороны и интересует async без раскраски — ставьте 0.16 и загляните в исходники stdlib: это самая чистая реализация «нераскрашенного» async среди мейнстримных языков на 2026 год.
Полные release notes — на ziglang.org. Обсуждение и отчёты о багах — в GitHub.