Разработка приложений и игр на языке Zig (современная альтернатива C)

Курс посвящен изучению языка Zig, который сочетает простоту C с безопасностью современных инструментов [apptractor.ru](https://apptractor.ru/info/articles/zig.html). Вы научитесь управлять памятью, использовать кросс-компиляцию и создавать производительные приложения и игры [habr.com](https://habr.com/ru/companies/ru_mts/articles/826094/).

1. Введение в Zig: история, установка среды и первая программа Hello World

Введение в Zig: история, установка среды и первая программа Hello World

Добро пожаловать в курс по разработке на языке Zig. Если вы здесь, значит, вы ищете инструмент, который сочетает в себе мощь и контроль C с удобствами современных языков программирования. Вы, возможно, слышали название "ZealC" или подобные вариации, но в индустрии этот язык известен как Zig. Это язык для тех, кто хочет понимать, как работает компьютер, и не боится брать на себя ответственность за управление ресурсами.

В этой статье мы разберем, откуда взялся Zig, почему он претендует на звание "наследника C", подготовим рабочее место и напишем нашу первую программу.

История и философия: почему Zig?

Язык программирования Zig появился в 2015 году. Его создатель, Эндрю Келли (Andrew Kelley), изначально пытался решить прикладную задачу — создать идеальный музыкальный сервер. В процессе работы он столкнулся с ограничениями существующих инструментов: C был слишком архаичным и небезопасным, C++ — переусложненным, а Rust имел слишком высокий порог входа thecodepunk.com.

Эндрю сформулировал философию, которая легла в основу Zig. Главная идея — отсутствие скрытого потока управления и отсутствие скрытых выделений памяти. В отличие от C++, где перегрузка операторов может скрывать вызов тяжелой функции за простым знаком +, или языков с Garbage Collector (Go, Java), где программа может внезапно остановиться для очистки памяти, Zig делает всё явным.

Место Zig в экосистеме

Чтобы понять, зачем нужен Zig, нужно взглянуть на спектр системных языков.

!Позиционирование Zig относительно других системных языков

Zig позиционируется не как "убийца C++" или "конкурент Rust", а именно как современная альтернатива C. Он сохраняет простоту C, но убирает его главные недостатки: макросы препроцессора и неопределенное поведение (undefined behavior), которое часто приводит к уязвимостям.

Ключевые особенности Zig:

* Совместимость с C: Zig умеет компилировать C-код и использовать C-библиотеки напрямую, без сложных прослоек (bindings). Вы можете включить заголовочный файл .h и сразу использовать его функции. * Comptime: Уникальная система выполнения кода во время компиляции. Это заменяет макросы и шаблоны, позволяя писать обычный код, который выполнится компилятором для генерации типов или данных. * Ручное управление памятью: Здесь нет сборщика мусора. Вы сами решаете, где и как выделять память, что критично для игр и высоконагруженных приложений hemaks.org.

Установка среды разработки

Инструментарий Zig удивительно компактен. В отличие от Visual Studio или Xcode, которые весят гигабайты, компилятор Zig — это один архив размером около 50-100 МБ, который уже содержит всё необходимое, включая систему сборки и стандартную библиотеку.

Шаг 1: Загрузка компилятора

Перейдите на официальную страницу загрузки или используйте пакетный менеджер вашей операционной системы.

Для Windows: Рекомендуемый способ — использовать пакетный менеджер winget или scoop, либо скачать ZIP-архив вручную.

Если вы скачиваете архив вручную:

  • Распакуйте его в удобное место (например, C:\zig).
  • Добавьте путь к папке с zig.exe в переменные среды (PATH) habr.com.
  • Для macOS: Самый простой способ — через Homebrew:

    Для Linux: Многие дистрибутивы уже имеют Zig в репозиториях, но они могут быть устаревшими. Лучше скачать бинарный файл с сайта или использовать snap:

    Шаг 2: Проверка установки

    Откройте терминал (командную строку) и введите:

    Если вы увидели номер версии (например, 0.13.0 или новее), значит, компилятор готов к работе. Также полезно проверить команду zig env, которая покажет информацию о текущем окружении.

    Шаг 3: Выбор редактора кода

    Zig поддерживается большинством современных редакторов. Рекомендуемый выбор — VS Code с официальным расширением Zig Language (или ZLS — Zig Language Server). Это обеспечит подсветку синтаксиса и автодополнение.

    Первая программа: Hello World

    Традиционно изучение языка начинается с вывода текста на экран. В Zig даже эта простая задача демонстрирует философию языка: явная обработка ошибок и отсутствие скрытых механизмов.

    Создайте файл с именем main.zig и откройте его в редакторе. Введите следующий код:

    Запуск программы

    В Zig компилятор является также и системой сборки, и исполнителем. Чтобы скомпилировать и сразу запустить программу, используйте команду run:

    Вы должны увидеть в консоли: Hello, World!

    Если вы хотите создать исполняемый файл (например, .exe на Windows), используйте команду:

    Разбор кода: что мы только что написали?

    Давайте разберем каждую строку, чтобы понять синтаксис Zig. Это фундамент, на котором мы будем строить сложные приложения.

    1. Импорт стандартной библиотеки

    * @import: Встроенная функция (built-in), начинающаяся с @. Она находит файл библиотеки и возвращает его как структуру. * const std: Мы сохраняем результат импорта в константу std. В Zig переменные и константы объявляются явно. const гарантирует, что значение не изменится.

    2. Объявление функции main

    * pub: Модификатор видимости. Функция main должна быть публичной, чтобы загрузчик операционной системы или среда выполнения могли её вызвать. * fn: Ключевое слово для объявления функции. * !void: Это тип возвращаемого значения. Здесь кроется важная особенность Zig. Символ ! означает Error Union (объединение с ошибкой). Это значит, что функция может либо успешно завершиться (вернув void, то есть ничего), либо вернуть ошибку. В Zig ошибки — это значения, а не исключения, как в C# или Java.

    3. Получение потока вывода

    Мы обращаемся к стандартной библиотеке std, модулю io (ввод-вывод), получаем дескриптор стандартного вывода getStdOut() и берем от него интерфейс writer(). Это более многословно, чем printf в C, но это показывает, что вывод — это работа с буфером, которая может завершиться неудачей.

    4. Вывод текста и обработка ошибок

    * try: Это ключевое слово для обработки ошибок. Поскольку запись в консоль теоретически может не удаться (например, поток закрыт), метод print возвращает ошибку. Ключевое слово try говорит: "Попробуй выполнить это. Если произойдет ошибка, немедленно верни её из функции main". Именно поэтому наша main возвращает !void. * "Hello, {s}!\n": Строка форматирования. {s} — это спецификатор для строк (string). В Zig спецификаторы типизированы: вы не можете случайно вывести число как строку без явного указания. * .{"World"}: Это аргументы для форматирования. Конструкция .{ ... } создает анонимную структуру. Функция print ожидает кортеж или структуру с аргументами. Даже если аргумент один, он должен быть внутри этой конструкции.

    !Поток данных и обработки ошибок при выводе текста

    Альтернативный способ вывода

    Для быстрой отладки часто используют другой метод, который пишет в stderr (стандартный поток ошибок) и не требует try (так как он предназначен для дебага и игнорирует ошибки):

    Этот способ проще для новичков, но в реальных приложениях (production) для основного вывода программы следует использовать stdout, как в первом примере.

    Система сборки (краткий обзор)

    Когда вы устанавливаете Zig, вы получаете не просто компилятор, а полноценную систему сборки. В больших проектах вы не будете писать zig run main.zig. Вместо этого вы создадите файл build.zig.

    Команда zig init (в более старых версиях zig init-exe) автоматически создаст структуру проекта:

    * build.zig: Скрипт сборки (написанный на самом Zig!). * src/main.zig: Исходный код.

    Это избавляет от необходимости изучать Makefiles, CMake или Ninja. Весь процесс сборки описывается на том же языке, на котором вы пишете программу apptractor.ru.

    Итоги

    Мы сделали первый шаг в мир системного программирования на Zig. Этот язык требует внимательности, но взамен дает полный контроль и высокую производительность.

    Краткое резюме:

  • Zig — это современный C. Он убирает неопределенное поведение и препроцессор, добавляя безопасность типов и удобные инструменты.
  • Инструментарий прост. Один бинарный файл содержит компилятор, стандартную библиотеку и систему сборки.
  • Явность превыше всего. В Zig нет скрытых аллокаций памяти или скрытого потока управления. Даже вывод текста требует явной обработки возможных ошибок через try.
  • Hello World. Мы узнали, как импортировать std, создавать функцию main с возвратом ошибки (!void) и использовать анонимные структуры .{} для передачи аргументов.
  • В следующей статье мы углубимся в типы данных и переменные, чтобы научиться хранить и обрабатывать информацию.

    2. Основы синтаксиса: типы данных, отсутствие скрытых потоков и обработка ошибок

    Основы синтаксиса: типы данных, отсутствие скрытых потоков и обработка ошибок

    В предыдущей статье мы настроили окружение и запустили первую программу. Теперь пришло время погрузиться в синтаксис. Zig — это язык, который ценит ясность и предсказуемость. Здесь нет скрытых механизмов, которые срабатывают за вашей спиной, и это фундаментально отличает его от C++ или Java.

    Мы разберем, как Zig хранит данные, почему он запрещает неявные преобразования типов и как его уникальная система обработки ошибок делает код надежнее.

    Переменные и константы

    В Zig данные разделяются на неизменяемые (константы) и изменяемые (переменные). Язык настоятельно рекомендует использовать константы везде, где это возможно. Это упрощает чтение кода: если вы видите const, вы гарантированно знаете, что значение не изменится.

    Синтаксис объявления выглядит так: ключевое_слово имя: тип = значение;

    Константы (const)

    Если тип можно вывести из значения, его можно опустить:

    Переменные (var)

    Если значение будет меняться в процессе выполнения, используйте var:

    Значение undefined

    В системном программировании иногда нужно выделить память под переменную, но не инициализировать её сразу (например, для оптимизации производительности). В Zig переменные обязаны быть инициализированы. Если у вас нет начального значения, вы должны явно указать undefined:

    Использование undefined означает, что в переменной лежит "мусор". Чтение такой переменной до записи в неё значения является ошибкой, которая в Debug-режиме приведет к краху программы (и это хорошо, так как помогает найти баги), а в Release-режиме — к неопределенному поведению.

    Базовые типы данных

    Zig предоставляет точный контроль над размером данных. В отличие от C, где int может быть 16, 32 или 64 бита в зависимости от архитектуры процессора, в Zig типы имеют фиксированный размер.

    Целые числа

    Типы обозначаются буквой i (signed, знаковые) или u (unsigned, беззнаковые) и количеством бит.

    * i8, u8 — 8 бит (аналог char или byte) * i16, u16 — 16 бит * i32, u32 — 32 бита * i64, u64 — 64 бита * isize, usize — размер машинного слова (зависит от архитектуры, 32 или 64 бита). Используется для индексов массивов и размеров памяти.

    Пример расчета диапазона значений для беззнакового числа:

    где — количество бит. Для u8 это .

    Числа с плавающей точкой

    * f16, f32, f64, f128 — соответствуют стандартам IEEE-754.

    Булев тип

    * bool — принимает значения true или false.

    Важно: В Zig нет неявного преобразования между числами и bool. Вы не можете написать if (1) { ... }, как в C. Вы обязаны писать if (1 != 0) { ... }.

    Массивы

    Массивы в Zig имеют фиксированную длину, которая известна на этапе компиляции. Синтаксис: [N]T, где N — количество элементов, T — тип.

    Если вы инициализируете массив сразу, можно позволить компилятору посчитать элементы, используя _ вместо числа:

    Для получения длины массива используется свойство .len.

    Отсутствие скрытых потоков управления

    Одна из главных фишек Zig — "No hidden control flow". Это означает, что код делает ровно то, что написано.

    В C++ или C# код a = b + c может вызвать:

  • Перегруженный оператор +.
  • Конструктор копирования при присваивании.
  • Исключение, если память закончилась.
  • В Zig:

  • Если b и c — числа, это просто сложение.
  • Здесь нет перегрузки операторов. Вы не можете переопределить + для своей структуры.
  • Здесь нет исключений (exceptions), которые могут внезапно прервать выполнение и перебросить вас в другой блок catch где-то выше по стеку.
  • Это делает код более многословным, но абсолютно прозрачным. Если функция может вернуть ошибку, это видно в её сигнатуре. Если функция выделяет память, она, скорее всего, принимает аллокатор как аргумент.

    Управляющие конструкции

    If

    Как упоминалось, if работает только с типом bool.

    While

    Цикл while имеет интересную особенность — секцию продолжения (continue expression). Она выполняется после каждой итерации.

    Здесь (i += 1) гарантированно выполнится, даже если внутри цикла сработает continue. Это защищает от вечных циклов, которые часто возникают в C при использовании continue.

    For

    Цикл for в Zig используется только для итерации по массивам, срезам (slices) и диапазонам. У него нет формы "инициализация; условие; шаг", как в C. Для этого используется while.

    Обработка ошибок

    Zig отказывается от исключений в пользу возвращаемых значений ошибок. Ошибки — это просто числа (enum), но компилятор предоставляет удобный инструментарий для работы с ними.

    Error Set

    Ошибки определяются как набор (set):

    Error Union Type (!)

    Тип !T означает: "Здесь может быть ошибка, ИЛИ значение типа T".

    Ключевое слово try

    Когда вы вызываете функцию, возвращающую ошибку, вы не можете просто проигнорировать её. Компилятор заставит вас обработать результат.

    Самый частый способ — пробросить ошибку выше. Ключевое слово try делает следующее: "Попытайся выполнить. Если успех — верни значение. Если ошибка — верни эту ошибку из текущей функции".

    !Логика работы оператора try: проверка на ошибку и ветвление потока выполнения

    Catch

    Если вы хотите обработать ошибку на месте, используйте catch:

    Также catch позволяет предоставить значение по умолчанию:

    Если parseNumber не сможет разобрать строку и вернет ошибку, переменная number станет равна 0.

    Итоги

    Мы рассмотрели фундамент синтаксиса Zig. Эти правила делают код строгим, но безопасным.

  • Строгая типизация: Используйте const по умолчанию. Типы имеют фиксированный размер (i32, u8). Нет неявных преобразований.
  • Явный поток управления: Нет перегрузки операторов и скрытых вызовов. Вы всегда видите, что делает код.
  • Ошибки как значения: Вместо исключений используются Error Unions (!type). Оператор try позволяет лаконично пробрасывать ошибки, а catch — обрабатывать их.
  • Безопасные циклы: while с выражением продолжения и for для итерации по коллекциям защищают от классических ошибок с индексами.
  • В следующей статье мы перейдем к одной из самых сложных, но важных тем системного программирования — работе с памятью и указателями.