
Не думаю, что за мою 45-летнюю карьеру какой-то другой язык удивлял меня сильнее, чем Zig. Могу с уверенностью сказать, что Zig — это не только новый язык программирования, но и, на мой взгляд, совершенно новый способ написания программ. Задача этого языка — далеко не только замена C или C++.
В этой статье я расскажу о фичах, показавшихся мне самыми привлекательными в Zig, а также вкратце опишу их. Моя цель — представить простые фичи, которые позволят программистам быстро приступить к работе с языком. Однако имейте в виду, что у него есть ещё множество других фич, влияющих на его популярность в нашей отрасли.
Компилятор
Он компилирует C и выполняет кросскомпиляцию
Наверно, самое невероятное достоинство компилятора Zig — способность компиляции кода на C. Оно связано с возможностью кросскомпиляции кода для запуска его на архитектуре, отличающейся от архитектуры машины, на которой он компилировался изначально, и это уже само по себе уникальная особенность. Уже лишь эти фичи, изначально доступные в языке, невероятно сильно повлияли на отрасль. Впрочем, мы будем в первую очередь говорить о том, как писать программы на Zig, и почему следует выбрать его вместо какого-то другого языка.
Установка компилятора
Установить компилятор достаточно просто. На странице скачивания Zinglang можно найти компилятор во множестве разных форматов для разных процессоров и операционных систем:

Например, в случае Windows 10 следует выбрать zip-файл x86_64 и скопировать его распакованное содержимое в нужную папку, скажем, в Program Files. Я изменил имя корневой папки на zig-windows-x86_64, потому что благодаря этому можно просто скопировать другую версию компилятора без необходимости изменения пути в переменной окружения Path.
Дальше нужно добавить путь к этой корневой папке в переменную окружения Path в «Дополнительных свойствах системы» (нажать на 1-4, вставить путь в 5 и нажать на OK в 6-8):

Сразу после указания пути к корневой папке Zig уже можно использовать компилятор в режиме CLI. Благодаря своей простоте это рекомендуемый способ использования.
Собираем программу «Hello World!» на Zig
Рекомендуется собирать программу «Hello World!» по инструкциям из раздела «Getting Started» сайта руководства. Также там представлена альтернативная процедура установки, в том числе для MacOS и Linux (крайне рекомендуется использовать «Installing manually»). Также рекомендуется изучить раздел «Language Basics» для более глубокого понимания языка Zig.
Основные концепции и команды
В приведённых ниже разделах будет представлено общее описание языка Zig. Их задача — позволить программисту научиться быстро приступить к программированию на Zig, освоив лишь некоторые базовые концепции и команды.
Далее представлено краткое описание того, как собирать программы и тестовые модули.
В конце приводится более глубокая информация о низкоуровневом программировании на Zig с более подробным описанием примеров.
Объявление переменных
Объявления переменных на Zig потенциально могут состоять из трёх частей. Первая часть содержит степень доступности (pub или ничего), за ней идёт слово var или const, а затем имя переменной. Вторая часть, отделённая от первой двоеточием, содержит объявление типа переменной. Третья часть — это инициализация переменной. В Zig обязательны только первая и третья части, что довольно непривычно для людей, программирующих на Java или C. Можно задаться вопросом, как же компилятор определяет тип переменной. В данном случае тип выводится из инициализации.
var sum : usize = 0; // объявление переменной из трёх частей
Переменные, для которых степень видимости не указана явным образом как pub, являются локальными для модуля, то есть доступ к ним невозможен снаружи файла исходников, в котором она объявлена (точно так же, как для переменных static в C). Не рекомендуется объявлять переменные, как pub, и рекомендуется создавать в модуле лишь небольшое количество функций, объявленных, как pub, чтобы снизить связанность и повысить целостность. Функции pub ведут себя, как API модуля.
Struct, анонимные struct и тестовые блоки
В Zig символ .{, закрываемый } — это литерал анонимной struct. Анонимные struct в основном используются для инициализации элементов другой структуры или для создания новой структуры с инициализированными элементами. .{ } — это пустой литерал анонимной struct. Слово struct, за которым идёт {, закрывающийся } — это объявление struct. Можно инициализировать переменную с типом, действующую в качестве псевдонима типа. Обратите внимание на тестовый блок, позволяющий компилировать и исполнять тесты без необходимости исполняемого файла.

Битовые поля
Битовые поля (bitfield) — это объявляемые поля с типами, имеющие конкретные размеры в packed struct. Указатели могут указывать на конкретное битовое поле следующим образом:

Циклы For
Синтаксис Zig понятнее, чем у C, только там, где обычно используется [0..8], в нём указывается открытый интервал: [0..9). Преимущество Zig: объявление типа i, её инициализация, проверка и инкремент выполняются автоматически.

Массивы
[_] определяет массив неизвестного размера. За ним должен следовать тип элементов (в данном случае u8, unsigned byte) и его инициализация между { и }. В показанном ниже примере инициализация гласит, что это массив неизвестного размера ([_]), в котором каждый элемент имеет тип u8 (unsigned byte) и инициализирован нулями ({0} ** 81). Стоит отметить, что размер также выводится из показателя повторения (81) инициализации ({0}).
var grid = [_]u8{0} ** 81;
На иллюстрации ниже показано тестовое окружение с циклом, взаимодействующим с массивом и складывающим его элементы. Конструкция try expect проверяет корректность sum.
Слово byte — это не тип и не зарезервированное слово в Zig. Здесь это переменная для хранения каждого из элементов массива на каждом шаге цикла. Переменная, объявленная между двумя | с массивом между скобками цикла for, всегда считается имеющей тот же тип, что и элементы массива.
Также стоит отметить, что usize — это естественный unsigned integer для платформы. Это значит, что на 64-битных машинах он имеет размер u64, а на 32-битных — u32.

Указатели на множественные элементы в Zig
Указатели на массивы могут использовать арифметику указателей, только если указатель явным образом объявлен как указатель на множественные элементы, в данном случае [*]const i32. Обратите внимание, что массив является const, то есть не может быть изменён, но указатель является var.

Разыменование указателей
В случае привязки к адресу отдельной позиции массива указатель невозможно изменять арифметикой указателей. В данном случае, const предотвращает лишь изменение значения другой прямой привязкой к адресу. Для разыменования указателей в Zig используется ptr.*, как показано ниже:

Break с метками
Zig может выполнять множество операций на этапе компиляции. Например, давайте инициализируем массив. Здесь применяется break с меткой. Блок помечен : после своего имени init и значение возвращается из блока при помощи break.

Этот синтаксис может показаться запутанным. 0.. означает бесконечный диапазон, начинающийся с нуля. В for переменные pt и i инициализуются адресом массива initial_value и нулём. В течение цикла инкремент обеих выполняется автоматически, и цикл останавливается сразу после последней позиции массива. Также обратите внимание, как разыменовывается указатель pt: pt.*.
Любопытен и способ объявления массива в блоке с меткой. Массив называется initial_value и содержит 10 позиций типа Point (объявленного позже). В Zig переменные объявляются обязательно. Явным образом можно отказаться от инициализации при помощи зарезервированного слова undefined.
Функции в Zig
Функции в Zig объявляются при помощи fn; по умолчанию они являются статическими (не могут импортироваться в другие файлы) в файле, где были объявлены, за исключением случаев, когда перед fn идёт pub. Функцию можно «встроить». Указателям функций предшествует const, а за ними идёт прототип функции.

ООП в Zig
Struct могут иметь функции. Ниже реализован простой стек. Этот стек объявляется и используется только внутри модуля, где он был определён; он получает доступ к другими структурам данных, не относящимся к нашему примеру. Стек может содержать не больше 81 элемента (как указано в объявлении stk), каждый с типом StkNode. Учтите, что операторов ++ и -- в Zig нет. Вместо них следует использовать эквиваленты += и –=. Указатель стека — это просто integer, используемый в качестве индекса массива stk.
Обратите внимание, что указатель self (self — это не зарезервированное имя, а стандартное наименование) не передаётся в явном виде как параметр, как можно было бы ожидать. Косвенно подразумевается, что это указатель на экземпляр стека, из которого вызывается функция. В примере ниже извлечение из стека можно вызвать при помощи stack.pop();. В этом случае указатель self соответствует указателю на stack. Этот указатель частично эквивалентен this в Java или C++.
Функция init() — это конструктор стека.
Обратите также внимание, что функции pop и push «встроены».

Сборка и исполнение программ на Zig
Сборка исполняемого файла
Для сборки исполняемой программы необходима функция main, которая, как и в программах на Java или C, обозначает точку входа в программу. Если эта функция существует, множество модулей можно скомпилировать в код исполняемого файла. Простая программа может содержать функцию main в том же файле, что и остальная часть программы. Во многих случаях также можно вставить функцию main в конец модуля, чтобы создать исполняемый файл для отдельной отладки модуля. После завершения отладки эту функцию можно закомментировать.
В таких ситуациях можно использовать следующую команду для компиляции program.zig и генерации исполняемой программы (в Windows это program.exe):
zig build-exe -O ReleaseFast program.zig
Чтобы избежать опечаток, команду можно сохранить в пакетный файл.
Исполнение тестовых блоков в модуле
Наверно, это самая лучшая фича Zig как языка программирования. Это окружение обычно используется для тестирования, но в нём можно и прототипировать.
Блок в Zig схож с блоком в C или Java, то есть это некий код между { и }. Тестовый блок — это блок, начинающийся с test "message" { и заканчивающийся на }, где "message" — это строка, содержащая сообщение, отображаемое при выполнении теста (в данном случае только слово message).
Тестовые блоки исполняются независимо от исполняемого файла. Готовый исполняемый файл не включает в себя тесты. Тестовые блоки в конкретном module.zig компилируются вместе со всем кодом этого файла и выполняются следующей командой:
zig test module.zig
Пример
В качестве реального примера покажем тестовый блок из модуля example.zig:
test " => testing set and print functions" {
set(
"800000000003600000070090200" ++
"050007000000045700000100030" ++
"001000068008500010090000400"
);
std.debug.print("\n" ++
"===================\n" ++
"Input Grid\n" ++
"===================\n",
.{}
);
print();
}
Обратите внимание, что в example.zig нет функции main, а потому он не может сгенерировать исполняемый файл, но его тестовый блок можно выполнить следующей командой:
zig test example.zig
Как и написано в сообщении, показанный выше тестовый блок тестирует функции set и print. Как видно из кода, set передаёт в качестве параметра строку десятичных цифр, за которой следует print (выводящий заголовок «Input Grid» ), за которым следует вызов функции print.
Результат выполнения будет следующим:

Вывод в Zig
Давайте обратим внимание на строку std.debug.print. Это вызов функции print в debug.zig в стандартной библиотеке Zig std. Первый параметр — это форматирующая строка, а вторая — анонимная struct (перед которой стоит точка), содержащая список переменных, отображаемых при помощи форматирующей строки. Так как в форматирующей строке формат отсутствует, struct пуста. Так и выполняется форматированный вывод. В данном случае он по умолчанию отобразится в stderr.
Всё это выглядит, как код на C, но здесь есть фундаментальное отличие. В C функция printf динамически интерпретирует форматирующую строку во время исполнения, а в Zig можно работать с литеральной строкой и списком переменных во время компиляции. Поначалу этот принцип трудно уяснить. Многое можно исполнять во время компиляции.
Отладка исполняемого файла
Работа с отладчиком обычно непроста, если в IDE не интегрирован отладчик (например, как в IDE Java наподобие Eclipse и Intellij IDEA) или отсутствуют интегрированные комплекты разработки (наподобие w64devkit для C/C++).
Большое неудобство при работе с отладчиками заключается в том, что необходимо интегрировать символы, что не только раздувает код бесполезной для программы информацией, но и требует компиляции в режиме отладки, при которой генерируется гораздо менее эффективный исполняемый код. Люди, имеющие опыт в сложных системах, знают, что это может быть очень длительным процессом.
Для устранения подобных проблем в Zig есть довольно удобный «хак»:
Встроенная @breakpoint
Когда исполняемый файл запускается в отладчике, эта встроенная функция останавливает программу в точке, где в исходный код вставлен @breakpoint();. Это полезная фича для отладки оптимизированного кода на Zig без необходимости символов.
Достаточно лишь трассировать переменные, за которыми нужно наблюдать, при помощи std.debug.print сразу после @breakpoint(); Благодаря этому мы сможем знать, каковы значения переменных в данный момент.
Пример
В качестве примера сгенерируем исполняемый файл для модуля debug_example.zig, содержащего следующую функцию main:
pub fn main() !void {
set(
"800000000003600000070090200" ++
"050007000000045700000100030" ++
"001000068008500010090000400"
);
}
Чтобы дважды выполнить сравнение с результатами example.zig, параметр, передаваемый функции set в этой main , совпадает со строкой, передаваемой set в тестовом блоке в example.zig, но на этот раз в функцию set вставляется следующий код:
if (c != 0) {
print();
std.debug.print(
"Current digit {}\nposition in string {}\n" ++
"line {}\ncolumn {}\ncode {b}\n",
.{c, k, i, j, code}
);
@breakpoint();
}
Можно сгенерировать исполняемый файл debug_example.exe при помощи следующей команды:
zig build-exe debug_example.zig
Дальше мы используем отладчик для вызова debug_example.exe. В этом случае я использовал gdb — отладчик C/C++ из w64devkit, но это мог быть любой отладчик для исполняемых программ. Внутри gdb необходимо запустить программу при помощи команды r и после этого нажать Enter. Обратите внимание, что программа вывела сетку с содержимым, а также переменными, остановившись в ожидаемом месте. Если затем ввести команду c и нажать на Enter, то продолжится трассировка содержимого сетки и переменных. После этого можно просто продолжать нажимать Enter (при этом повторяется последняя команда, то есть в данном случае c). После многократного нажатия на Enter до завершения программы значения в сетке будут соответствовать значениям, показанным в тестовом блоке в example.zig, потому что оба примера передают set одинаковый параметр.

Низкоуровневое программирование на Zig
Ознакомившись с введением и примерами, вы уже можете приступать к написанию общих программ на Zig. Ниже представлен более глубокий анализ интересных низкоуровневых фич языка, уже использованных в примерах.
Смысл показанных выше примеров заключается в создании модуля, задающего (инициализирующего) и отображающего сетку 9x9. В этой матрице будет храниться сетка судоку, то есть в ней будут содержаться только десятичные цифры. Инициализация сетки должна гарантировать, что сетка удовлетворяет условиям игры в судоку, то есть не содержит ошибок.
В то же время это будет отличная возможность показать низкоуровневые фичи Zig (по крайней мере, самые выдающиеся), и наши примеры хорошо для этого подходят.
Гипотеза, лежащая в основе этих примеров, заключается в том, чтобы представить цифру сетки в качестве бита в позиции, заданного её значением. Это представление достаточно удобно для определения того, присутствует ли уже цифра в сетке (это основное правило сеток судоку). При всём этом, она будет зашифрована так, что её можно использовать только внутри модуля.
Представление матрицы
Чтобы значения легко понимались человеком, цифры хранятся в матрице как стандартные целые u8. Хоть входная сетка в примерах задаётся в строковом формате, символы ASCII преобразуются в целые u8. Хранение цифр в сетке организовано линейно, строка за строкой, в массиве с 81 позицией, называемом в примерах grid:
var grid = [_]u8{0} ** 81; // Линейно хранящаяся сетка судоку
Для проверки корректности сетки необходимо получать доступ к элементам по соответствующей строке и столбцу. Иными словами, необходимо получать доступ к элементам в матрице. Стратегия заключается в создании массива указателей с 9 позициями, каждая из которых указывает на начало каждой строки сетки. Блоки кода в принципе не могут возвращать значение, но в Zig это возможно благодаря break с метками:
const matrix = fill9x9: { // массив матрицы, обеспечивающий доступ
var m : [9][*]u8 = undefined; // к элементу сетки, как матрицы,
var pt : [*]u8 = &grid; // то есть: элемент = matrix[i][j]
for (0..9) |i| { //
m[i] = pt; // хранит указатели на каждую строку
pt += 9; // в каждой позиции матрицы
} //
break :fill9x9 m; // инициализирует матрицу с помощью m
};
В конце цикла m возвращается снаружи блока при помощи команды break :fill9x9 m;. Следует учесть, что fill9x9 соответствует имени метки, расположенной сразу после начала блока.
Если i и j — это строка и столбец элемента, то к любому элементу сетки можно получить доступ при помощи следующей записи:
element = matrix[i][j]
Представление десятичных цифр в виде битов
Ключевая концепция, используемая здесь — это замена целочисленной десятичной цифры i целочисленным code:
i ∈ [1,9] → code = 2ⁱ⁻¹
i = 0 → code = 0
Иными словами, единственный бит code, которому присваивается 1 — это бит в позиции i-1, если i находится между 1 и 9, в противном случае все биты code равны нулю.
Значения code для каждой цифры
В таблице ниже показаны отношения между цифрами и их двоичным представлением:

Вычисление code в Zig
Значение code вычисляется в функции set при помощи оператора сдвига влево, только если c не равно нулю:
code = @as(u9,1) << (c-1);
Чтобы операция могла скомпилироваться, и результат операции мог быть привязан к переменной, в Zig константы должны иметь нужный размер. В данном случае, code имеет объявленный тип u9. Это ещё одно фундаментальное качество Zig — возможность наличия переменных с произвольным битовым размером. Как видно из таблицы выше, максимальное значение code равно 256, а для его представления нужно не менее 9 битов. Встроенная функция @as позволяет привести константу 1 к типу u9.
Представление сетки при помощи битовых полей
Представив цифры в виде битов, можно отобразить сетку гораздо более простыми способами.
Сетка битовых полей строк
Массив lines отражает всю сетку, представляя каждую строку 9-битным integer, где каждый бит представляет десятичную цифру, которую можно представить в строке:
var lines = [_]u9{0} ** 9;
Благодаря этому, просто получая доступ к строке i элемента в сетке 9x9, можно понять, присутствует ли конкретная цифра в этой строке, выполнив побитовое и ( & ) c code цифры:
lines[i] & code
Если результат этой операции равен нулю, то цифра ещё не представлена в строке i. Противоположное означает наличие дубликата.
Сетка битовых полей столбцов
Массив columns отражает всю сетку, представляя каждый столбец как 9-битный integer:
var columns = [_]u9{0} ** 9;
Благодаря этому для проверки наличия конкретной цифры в столбце j достаточно простого доступа к этому массиву с этим столбцом элемента в сетке 9x9. Для этого нужно всего лишь выполнить побитовое и ( & ) с code цифры:
columns[i] & code
Если результат этой операции равен нулю, это означает, что цифры ещё нет в столбце j . Противоположное означает наличие дубликата.
Правила судоку
Предположим, что у нас есть пустая сетка судоку, как это было, когда мы заполняли в примерах сетку входной строкой. Новая цифра, вставляемая в пустой элемент, не должна присутствовать во всей строке, столбце или ячейке, содержащей новый элемент.
Допустим, что эта сетка уже инициализирована:

Ячейка — это каждая из девяти сеток 3x3, ограниченных жирными линиями. Здесь важно понять, что каждый элемент в сетке 9x9 имеет уникальную строку, столбец и ячейку, содержащую этот элемент.
Например, первая ячейка сетки содержит значения 3, 5, 6, 8 и 9. Следовательно, в ней отсутствуют значения 1, 2, 4 и 7. Предположим, что мы хотим поместить значение 7 в одно из пустых мест первой ячейки. Очевидно, что нельзя поместить его в единственный пустой элемент первой строки, потому что в этой строке уже присутствует 7. Нельзя её разместить и в единственном пустом месте первого столбца, потому что 7 уже есть в этом столбце. Можно поместить 7 только в один из двух пустых элементов второй строки. Но мы не можем знать точно, какой из них подходящий.
Давайте теперь изучим вторую ячейку, содержащую значения 1, 5, 7 и 9. Мы видим, что единственный возможный элемент в этой ячейке, куда можно поместить 8 — первая строка в пустой позиции справа от значения 7.
Массивы lines и columns проверяют наличие дубликатов в строках и столбцах. Значит, требуется новый массив для проверки дубликатов в ячейках.
Сетка битовых полей ячеек
Массив cells отражает всю сетку, представляя каждую ячейку в виде integer из 9 бит:
var cells = [_]u9{0} ** 9; // все элементы каждой ячейки
И здесь всё становится сложнее. Нельзя получить доступ к cells непосредственно при помощи строки или столбца. Было бы проще, если бы мы могли получать доступ к cells, как к матрице 3x3. Это можно сделать имитацией того, что мы сделали для матрицы 9x9, то есть заполнив массив cell следующим образом:
const cell = fill3x3: { // массив cell обеспечивает доступ
var m : [3][*]u9 = undefined; // к элементам ячейки, как к матрице,
var pt : [*]u9 = &cells; // cell[cindx[i]][cindx[j]]
for (0..3) |i| { //
m[i] = pt; // сохраняет указатель на каждую строку
pt += 3; // в каждой позиции ячейки
} //
break :fill3x3 m; // инициализирует ячейку с помощью m
}; //
Но теперь нужно определить строку и столбец в матрице cell по строке и столбцу элемента в исходной сетке 9x9. Можно использовать целочисленное деление, чтобы поделить строку и столбец на 3 и получить индексы, но известно, что операция деления медленная. Два деления усугубляют ситуацию. Можно использовать следующий массив, чтобы представить результат деления:
const cindx = [_]usize{ 0,0,0, 1,1,1, 2,2,2 };
Благодаря этому, просто получив доступ к этой матрице со строкой i и столбцом j элемента в сетке 9x9, можно понять, присутствует ли конкретная цифра в ячейке этого элемента, выполнив побитовое и ( & ) c code цифры:
cell[cindx[i]][cindx[j]] & code
Если результат этой операции равен нулю, то цифра ещё не присутствует в ячейке, в противном случае есть дубликат.
Проверка дубликатов элемента
Полная проверка наличия дубликатов элемента выполняется сочетанием побитового или ( | ) всех предыдущих элементов в той же строке, столбце и ячейке с последующим побитовым и ( & ) с code элемента:
if (((lines[i]|columns[j]|cell[cindx[i]][cindx[j]])&code) != 0) {
unreachable;
}
Если результат равен нулю, значит, элемент пока не существует в этой строке, столбце или ячейке. Если результат ненулевой, то программа останавливает работу, потому что пытается выполнить команду unreacheable. Это простейший способ явного обозначения ошибки исполнения в Zig. Обратите внимание, что код в функции set также выводит подробности о том, где произошла ошибка.
Например, если в строке, передаваемой set, заменить '0' сразу после первой '8' на '5', то при тестировании example.zig будет выведена следующая ошибка:

Это вызвано тем, что в столбце 1 уже есть значение 5 в строке 3, как и написано в сообщении об ошибке. Причина ошибки заключается в панике, вызванной попаданием в недостижимый код в функции set.
Изменение структур данных
В функции set двойной цикл for строка за строкой копирует каждый новый элемент из входной строки s в сетку, как показано ниже (в переменной k хранится индекс нового входящего символа в строке s):
for ( 0..9 ) |i| {
line = matrix[i];
for ( 0..9 ) |j| {
c = @intCast(s[k]-'0');
if (c != 0) {
code = @as(u9,1) << (c-1);
⋮ // остальная часть кода
}
line[j] = c;
k+= 1;
}
}
Символ преобразуется в u4 (переменная c) вычитанием из него '0'. Если новый элемент, вставляемый в сетку, не равен нулю ( c != 0 ), то code (вычисленный командой сдвига влево) копируется в каждую из сеток выполнением побитового или ( |= ) с соответствующей сеткой:
lines[i] |= code;
columns[j] |= code;
cell[cindx[i]][cindx[j]] |= code;
Никакой явной проверки того, что значение c находится в интервале от 1 до 9, не требуется, потому что при выполнении операции сдвига произойдёт переполнение. Например, заменив '0' сразу после первой '8' входной строки на ':' в строке, передаваемой set, мы получим при тестировании example.zig следующую ошибку:

Если заменить тот же '0' на '/', то возникнет схожая ошибка исполнения. Программа будет работать, только если все значения находятся в интервале от 1 до 9, то есть если входная сетка содержит только десятичные цифры.
В вебе во многих сетках судоку '0' также обозначается как '.'. Именно поэтому в функции set есть следующая строка:
if (s[k] == '.') c = 0;
Это позволяет удобно обойти операцию сдвига, потому что значение c равно нулю.
Прототипирование и устойчивость
Принудительные ошибки, показанные в двух предыдущих разделах, демонстрируют важные фичи Zig, которые могли бы оказаться незамеченными. Одна из них — это устойчивость Zig. Как мы видели, в случае операции сдвига не допускается никакое ошибочное поведение, и ситуация перехватывается во время исполнения.
Кто-то может подумать, что все усилия по развитию Zig направлены на эффективность, но это типичный случай того, как производительностью жертвуют в пользу устойчивости. Если ваша главная цель — производительность, это может показаться спорным выбором. Например, в C потеря бита при операции сдвига — это проблема программиста, зато это повышает производительность определённой ассемблерной команды.
Ещё одна фича, продемонстрированная пару разделов назад — возможность использования тестовых блоков для прототипирования. Потенциал этой фичи огромен, хотя в нашем примере она используется лишь для отладки определённых ситуаций в случаях ошибок.
Уже сами по себе эти фичи демонстрируют большую мощь, редко встречающуюся в языках программирования, и в особенности в компилируемых.
Заключение
Всё это довольно неожиданно и заставляет меня задуматься о том, что множество преимуществ, которые ранее встречались только в интерпретируемых языках, постепенно мигрируют в компилируемые языки с целью обеспечения повышенной производительности.
Схожесть Zig с интерпретируемыми языками потрясает, и в особенности его концепция исполнения во время компиляции, к сожалению, рассмотренная в этой статье недостаточно подробно. Этот аспект Zig, с одной стороны, делает его уникальным и мощным, с другой стороны, усложняет его освоение.
В основном я сделал упор на знакомство с основами и простыми аспектами Zig, благодаря которым он настолько крут; однако существуют и другие довольно впечатляющие аспекты.
Показанные в статье примеры — это упрощённые версии более сложных программ для решения судоку, написанных также на Java и C. Документация в репозитории подробно объясняет большинство структур и алгоритмов, использованных для решения задачи.
Комментарии (7)

andreymal
12.11.2025 13:47программа останавливает работу, потому что пытается выполнить команду
unreacheable. Это простейший способ явного обозначения ошибки исполнения в Zig.При компиляции в режиме ReleaseFast программа НЕ останавливает работу, в результате чего полностью ломается и начинает творить дичь
Из этого можно сделать неутешительный вывод, что сам автор оригинала не знает даже Zig (не говоря уже о R*кхе-кхе*)

andreymal
12.11.2025 13:47Никакой явной проверки того, что значение
cнаходится в интервале от1до9, не требуется, потому что при выполнении операции сдвига произойдёт переполнение.И здесь то же самое: в режиме ReleaseFast переполнение НЕ проверяется, из-за чего sudoku.zig падает с сегфолтом, а example.zig пишет «All 1 tests passed» и не заморачиваясь печатает мусор вместо цифр в Input Grid

kez
12.11.2025 13:47Не думаю, что за мою 45-летнюю карьеру какой-то другой язык удивлял меня сильнее, чем Zig. Могу с уверенностью сказать, что Zig — это не только новый язык программирования, но и, на мой взгляд, совершенно новый способ написания программ.
Иронично что после такого громкого начала идёт перечиследние довольно обыденных вещей, которыми обладает большое количество современных языков.
Откровенно не понимаю, почему эта статья собрала так много голосов на хакер ньюсе.
В зиге действительно есть интересные концепты, хотелось бы сфокусироваться на них.

Octagon77
12.11.2025 13:47Чем хороша статья которая перевод? Во первых, не надо цепляться к тексту, как это делается местами выше - разговор то с переводчиком. Во вторых, перевод обычно с буржуинского, а буржуины - мастера искусства продавать. Часто слышу я про Zig, но именно на этот раз я как-то сразу
yay zigи оно мне
5 extra/cargo-zigbuild 0.20.1-1 (1.1 MiB 3.5 MiB) Compile Cargo project with zig as linker 4 extra/zigbee2mqtt 2.6.3-1 (39.4 MiB 196.8 MiB) A Zigbee to MQTT bridge 3 extra/zls 0.15.0-2 (1.2 MiB 3.5 MiB) A language server for Zig 2 extra/zig0.14 0.14.1-2 (20.1 MiB 159.2 MiB) a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software 1 extra/zig 0.15.2-2 (23.8 MiB 186.4 MiB) a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software ==> Packages to install (eg: 1 2 3, 1-3 or ^4) ==> 1 3И тут обращает на себя внимание пакет 5 - я не знаю зачем менять в Cargo линковщик, но Zig точно оказался как химия - широко простёр руки свои в дела человеческие. Поэтому я сразу за телефон и в Termux - многократно уже убедился, что всё интересненькое в пакетах Termux есть, а постылого ничего и нету, рекомендую лайфхак. И там zig да zls как с куста
Так что
$ zig version 0.15.2 $Продано, можно поздравлять автора и переводчика. В Termux, правда, 0.15.1.

NeoCode
12.11.2025 13:47Любой новый язык программирования это всегда интересно. А в случае с zig интересно зачем они придумали нестандартный синтаксис составных литералов
.{ }, чем общепринятый последнее время JSON-like синтаксис не устроил?
andreymal
*шёпотом* ...Rust... *подготовка к уворачиванию от тапков*