#
Глава 3. Ваша первая программа Zig
В этой главе мы возьмем классическое клише — программу «Привет, мир!» и превратим ее в нечто более... познавательное. Да, это всеми любимая первая программа, но мы разберем ее по частям, как лягушку на уроке биологии.
Что вы собираетесь делать? Что ж, помимо написания своей первой программы на Zig, вы научитесь создавать проект на Zig с нуля, работать с переменными и делать первые шаги в отладке. Вы также познакомитесь с тем, как в Zig относятся к строкам — с любовью и ненавистью. А к концу курса вы уже будете компилировать и запускать собственные программы вручную, как настоящий системный программист, которому не нужны подстраховки.
В этой главе мы рассмотрим следующие темы:
- «Привет, мир!»: ваш первый опыт работы с Zig
- Базовая отладка
- Переменные
- Сборка проектов Zig
#
«Привет, мир!»: ваш первый опыт программирования на Zig
Ну вот и все! Пришло время сорвать покровы и написать нашу самую первую программу. Да, мы приступаем к печально известной программе «Привет, мир!», но подождите… мы не просто выведем текст на экран и на этом остановимся. Мы разберем ее по частям, изучим каждый токен, пока вы не станете мастером программы «Привет, мир!». Копирование и вставка фрагментов кода — не лучший способ учиться, верно? Давайте создадим эту программу с нуля и разберем ее по частям. Сначала создайте файл с именем main.zig и откройте его в своем любимом текстовом редакторе. На этом чистом холсте мы будем творить. Каждая программа на Zig начинается с функции main, которая в Zig выглядит так:
pub fn main() void { ... }
Давайте разберёмся. Это публичная функция с именем main, она не принимает никаких аргументов и ничего не возвращает — отсюда и void.
void
Сейчас это слово может показаться скучным, но не волнуйтесь, оно делает своё дело — упрощает код. И да, это уже полноценная программа на Zig.
Теперь попробуйте запустить её:
$ zig run main.zig
Ничего не происходит! Это отличная новость — ошибок нет. Как будто мы начинаем с чистого листа, а это именно то, что нам нужно на данном этапе. Но давайте будем честны: мы проделали весь этот путь не для того, чтобы ничего не увидеть. Нам нужно что-то показать.
#
Подключение стандартной библиотеки
Чтобы что-то отобразить на экране, мы воспользуемся стандартной библиотекой Zig. Давайте введем нашу самую первую встроенную функцию: @import. Вот как это выглядит:
const std = @import("std");
В этой строке мы импортируем стандартную библиотеку Zig и привязываем ее к константе std. Это позволяет нам использовать различные полезные инструменты из библиотеки, в том числе функции для вывода текста. Представьте, что ваша программа получила доступ к набору утилит, как у швейцарского армейского ножа.
В этой строке много всего происходит, так что давайте не будем торопиться и разберём её по частям. Это первый шаг к тому, чтобы собрать всё воедино и создать более динамичную программу.
Примечание
Пакет std всегда доступен в Zig — это как надёжный инструмент, который всегда под рукой.
#
Импорт пакетов
Теперь, когда мы импортировали стандартную библиотеку, давайте немного углубимся в то, что на самом деле происходит за кулисами. Возможно, вы заметили, что название функции начинается с символа "@" — это означает, что это встроенная функция, предоставляемая непосредственно компилятором Zig. Добро пожаловать в Zig lingo! Постепенно вы привыкнете к этим встроенным функциям. В настоящее время мы уделяем особое внимание @import. Сигнатура @import выглядит следующим образом:
@import(comptime path: []const u8) type
На первый взгляд выглядит немного сложно, не так ли? Давайте разберем официальное объяснение (https://ziglang.org/documentation/master/#import):
Эта функция находит файл Zig, соответствующий пути, и добавляет его в сборку, если он еще не добавлен. Исходные файлы Zig неявно являются структурами с именем, равным базовому имени файла без расширения. @import возвращает тип структуры, соответствующий файлу.
Проще говоря, @import принимает путь к файлу, загружает его и позволяет работать с его содержимым как со структурой. Так мы получаем доступ к таким вещам, как стандартная библиотека.
Что ж, приготовьтесь — вот ваш первый сюрприз: в Zig нет строк. Да, вы не ослышались. Никаких строк. Я знаю, что это дерзко и, может быть, даже немного бунтарски, но, поверьте, Зиг не делает ничего «традиционного», если в этом нет смысла. Спойлер: строки не подходят. Вместо них используются фрагменты байтов, что звучит гораздо более брутально, не так ли? Они эффективны, низкоуровневы и идеально подходят для системного программирования, где главное — производительность.
Фрагменты (Slices)
Фрагмент в Zig — это, по сути, ссылка на последовательность значений - в данном случае байтов. Представьте его как указатель на массив, но с известной длиной. Он легкий, эффективный и гибкий, что хорошо согласуется с философией Zig, ориентированной прежде всего на производительность.
Хотите узнать больше о срезах и других структурах? Не волнуйтесь, мы подробнее рассмотрим, как Zig организует данные, в Главе 7 "Организация данных". Там вы познакомитесь с срезами, массивами и другими мощными структурами, которые делают Zig таким гибким и эффективным языком. А пока просто запомните: в Zig «строки» на самом деле представляют собой просто срезы байтов, и это один из многих способов, с помощью которых Zig дает вам больше контроля над вашими данными.
Поэтому для вывода информации мы используем std.debug.print. Давайте разберем этот пример:
const std = @import("std");
pub fn main() void {
std.debug.print("Hello Zig! \n", .{});
}
Эта программа настолько проста, насколько это возможно, но в ней много всего происходит. Функция std.debug.print ожидает два параметра: строку форматирования (которая, напомним, представляет собой просто фрагмент байтов) и аргументы. В нашем случае мы передаем "Hello Zig! \n" в качестве строки форматирования и .{} в качестве аргументов.
Что это за .{}? А, вы заметили этот странный синтаксис? Что ж, это просто своеобразный способ, которым Зиг говорит: «Эй, тут нет аргументов, но я оставлю заполнитель на случай, если он тебе понадобится». Сейчас это выглядит странно, но скоро вы будете использовать .{} как нечто само собой разумеющееся. Пора запускать нашу программу!
#
zig run
Вы используете zig run для компиляции и запуска программ на языке Zig за один раз. Но задумывались ли вы когда-нибудь, что на самом деле происходит, когда вы вводите эту команду? Давайте приоткроем завесу тайны.
Когда вы используете zig run, это не волшебство — команда просто выполняет за вас две вещи:
- Компилирует исходный код в исполняемый файл.
- Запускает этот исполняемый файл сразу после
Но если вы хотите лучше разобраться в этом процессе, давайте разберем его на составляющие и сделаем все сами, шаг за шагом.
#
Шаг 1. Сборка исполняемого файла вручную
Вместо использования zig run попробуйте сделать так:
$ zig build-exe main.zig
Эта команда указывает Zig на необходимость собрать исполняемый файл вручную. После выполнения команды загляните в свой каталог. Ага! Вы заметили, что появились два новых файла?
main: это только что собранный исполняемый файлmain.o: это объектный файл, содержащий скомпилированный машинный код, из которого состоит ваша программа (только в Windows).
#
Шаг 2. Запуск исполняемого файла
Теперь, когда мы сами собрали исполняемый файл, давайте запустим его:
./main
Вуаля! Вы увидите тот же результат, что и при использовании zig run. Это результат ручной сборки и запуска программы, то есть именно то, что делает zig run, но без промежуточных шагов.
#
Зачем это делать?
Вы можете спросить: «Зачем мне возиться с zig build-exe, если zig run делает все за один шаг?» Что ж, zig run отлично подходит для небольших и быстрых программ, но если вы знаете все этапы процесса, то можете лучше контролировать его. По мере усложнения ваших проектов вам придется работать с объектными файлами, настраивать конфигурации сборки и оптимизировать процесс компиляции кода. Понимание этого процесса открывает возможности для более продвинутой оптимизации.
Так что наслаждайтесь простотой zig run, когда это соответствует вашим потребностям, но помните, что при необходимости вы можете использовать ручной подход. Независимо от того, используете ли вы zig run для решения простых задач или zig build-exe для более детальной настройки, ключевым элементом являются переменные. Переменные — основа любой программы, и понимание того, как с ними работает Zig, поможет вам лучше понять, как ведут себя и работают ваши программы.
Переходя от изучения механики создания исполняемых файлов к пониманию переменных, помните: чем лучше вы разбираетесь в особенностях языка Zig, тем больше у вас возможностей контролировать каждый этап процесса разработки. Давайте разберемся, как переменные влияют на структуру и эффективность вашего кода.
#
Переменные
Позвольте мне сказать вам кое-что прямо: если вы относитесь к тому типу людей, которые любят использовать var везде, я это пойму. Вам нравится хаос, неопределенность и жизнь на грани. Но давайте посмотрим правде в глаза: const — ваш более умный, надежный и, откровенно говоря, более симпатичный друг. Почему? Потому что использование const означает меньше сюрпризов как для вас, так и для компилятора. Это все равно что сказать вселенной: “Эй, эта переменная высечена на камне. Нет, я не собираюсь его изменять. Да, можешь расслабиться». Компилятору это нравится. И мне тоже.
В языке Zig, где оптимизация имеет решающее значение, а неожиданные проблемы во время выполнения недопустимы, использование const позволяет компилятору делать множество умных вещей, например не снижать производительность. Конечно, вы можете использовать var там и сям, но каждый раз, когда вы это делаете, знайте, что вы создаете дополнительную работу для себя в будущем — и, поверьте, в будущем вы себя возненавидите.
Начнем с использования ключевого слова const для присвоения значений. После того как значение присвоено с помощью const, оно становится неизменяемым. Считайте, что это обязательство, которое ваш компилятор ценит, поскольку оно позволяет лучше оптимизировать код.
const x = 1234;
pub fn main() void {
const y = 5678;
std.debug.print("x = {}, y = {}\n", .{x, y});
// Если раскомментировать следующую строку, компилятор выдаст ошибку, поскольку y — константа.
// y += 1;
}
При запуске этого кода вы без каких-либо сюрпризов увидите на экране значения x и y. Но если вы попытаетесь изменить y, компилятор выдаст ошибку:
$ zig run main.zig
main.zig:9:7: error: cannot assign to constant
y += 1;
~~^~~~
Если вам действительно нужно изменить переменную, используйте var. Это обеспечивает возможность изменения, но будьте осторожны: за такую гибкость приходится платить потенциальной непредсказуемостью:
pub fn main() void {
var y: i32 = 5678;
y += 1; // Изменение переменной
std.debug.print("y = {}\n", .{y});
}
На этот раз значение y может изменяться, и результат будет выведен на экран:
$ zig build-exe mutable_var.zig
$ ./mutable_var
y = 5679
Есть одна загвоздка! Zig требует, чтобы все переменные были инициализированы. Это здорово — так вы с самого начала работаете с корректными данными. Если вы забудете инициализировать переменную, Zig остановит вас до того, как ситуация выйдет из-под контроля.
pub fn main() void {
var x: i32; // Объявлена, но не инициализирована!
// Если раскомментировать следующую строку без инициализации x, возникнет ошибка.
// std.debug.print("x = {}\n", .{x});
x = 1; // Инициализация выполнена правильно
std.debug.print("x = {}\n", .{x});
}
Попробуйте запустить код без инициализации x, и вы получите сообщение об ошибке:
$ zig build-exe var_must_be_initialized.zig
main.zig:2:15: error: expected '=', found ';'
var x: i32;
^
Бывают случаи, когда у вас нет готового начального значения. В таких ситуациях Zig предоставляет неопределенное значение, которое означает: «Я пока не знаю, что это такое». Но будьте осторожны — работать с неопределенным значением рискованно.
pub fn main() void {
var x: i32 = undefined; // x неинициализирован и сейчас может быть чем угодно.
std.debug.print("x до инициализации = {}\n", .{x});
x = 42; // Теперь x присвоено правильное значение.
std.debug.print("x после инициализации = {}\n", .{x});
}
При выполнении этого кода Zig инициализирует x неопределённым значением (которое может быть любым), пока вы явно не присвоите ему нужное значение:
$ zig build-exe assign_undefined.zig
$ ./assign_undefined
x до инициализации = -1431655766
x после инициализации = 42
Является ли var злом? Не совсем. У него есть своё применение, но оно ограничено. Используйте var, только если вам действительно нужно изменить значение, например в циклах или алгоритмах, требующих обновления на месте. По возможности используйте const. Это сделает ваш код предсказуемым, читабельным и оптимизированным. Используйте var только в том случае, если у вас нет другого выбора, и всегда держите его под контролем.
Теперь, когда мы разобрались, что такое переменные, давайте перейдем к самому интересному: где они хранятся, как себя ведут и почему не стоит пытаться придумывать для них хитроумные названия. Кстати, давайте поговорим о затенении.
#
Затенение
Zig не любит сюрпризов, и вам тоже не стоит!
Давайте поговорим о области видимости. Вы когда-нибудь заходили в комнату и видели, что на ком-то надета точно такая же рубашка, как у вас? Неловко, правда? А теперь представьте, что в Zig вы попытаетесь проделать тот же трюк с именами переменных. Компилятор посмотрит на вас с осуждением и откажется работать. Почему? Потому что в Zig затенение области видимости просто не допускается. Это не JavaScript, где затенение переменных — практически неотъемлемая часть языка. Нет, в Zig, если вы хотите повторно использовать имя переменной во вложенной области видимости, вам не повезло.
Вот пример, демонстрирующий, как Zig обрабатывает затенение и что происходит при попытке его использовать:
const std = @import("std");
pub fn main() void {
const x = 10; // Первое объявление x
{
const x = 20; // Ошибка: нельзя повторно объявить 'x' во внутренней области видимости
std.debug.print("Внутренняя область видимости x = {}\n", .{x});
}
std.debug.print("Внешняя область видимости x = {}\n", .{x});
}
В этом коде происходит следующее:
const x = 10объявляетxво внешней области видимости- Если вы попытаетесь снова объявить
xво внутреннем блоке (const x = 20;), Zig выдаст ошибку во время компиляции, поскольку в Zig не допускается затенение. Эта ошибка помогает избежать путаницы, которая может возникнуть при использовании одного и того же имени переменной в разных областях видимости.
#
Идентификаторы
Вам может казаться, что вы умничаете, называя переменные причудливыми символами или числами, как какой-нибудь бунтующий хакер из 90-х. Zig это не нравится. Идентификаторы должны начинаться с буквы или символа подчеркивания. Что-то еще? Извините, компилятор посмеется и выдаст ошибку. Другими словами, нет, вы не можете назвать переменную 1337_cr3w.
Рассмотрим этот пример:
var valid_name = 42; // Хорошо
var _anotherValidName = 100; // Тоже хорошо
var 1invalidName = 123; // Нет, это вызовет проблемы
Если вам совершенно необходимо использовать что-то абсурдное, например 1SmallStep4Man, или идентификатор с пробелами, есть решение, синтаксис Zig @"", который говорит: «Хорошо, я разрешаю, но только потому, что вы воспользовались запасным вариантом». Используйте его с умом или вообще не используйте — если только вы не из тех, кому нравится странный синтаксис, чтобы чувствовать себя особенным.
Но что, если вам нужно назвать что-то, что не соответствует этим правилам, например при подключении внешней библиотеки? Возможно, вы имеете дело с именем, в котором есть пробелы или которое начинается с цифры:
const @"идентификатор с пробелами" = 42;
const @"1ExternalLibIdentifier" = 100;
Этот синтаксис позволяет нарушать обычные правила, но будьте осторожны — используйте его только в случае крайней необходимости. В большинстве случаев лучше придерживаться стандартных правил именования, чтобы код был чистым и понятным. Но если вы работаете со сторонними библиотеками, которые используют странные соглашения об именовании, вам на помощь придет @"".
Поэтому старайтесь использовать простые идентификаторы и применяйте синтаксис @"" только в тех случаях, когда вам приходится иметь дело с неудобными названиями сторонних библиотек.
#
Коротко о примитивных типах
Давайте начистоту: вы читаете не какое-то запылившееся руководство из 70-х, и мы точно не собираемся утомлять вас подробным разбором всех примитивных типов, которые есть в Zig. Мы так не работаем. Вместо этого мы сосредоточимся на особенностях и строго необходимых деталях, чтобы вы могли двигаться дальше и изучать язык, не отвлекаясь.
Если вы любознательны и хотите изучить все примитивные типы в удобном для вас темпе, полный список можно найти здесь: https://zig-lang.ru/guides/0.15.2/values/#primitive-types.
Здесь есть все, что вам может понадобиться, если вы любите приключения.
Я прикрою тебя
Если в следующих главах произойдет что-то странное или неожиданное, я вас предупрежу. А теперь давайте перейдем к более интересным вещам и пропустим длинные и скучные описания типов. Вы здесь для того, чтобы изучать язык, а не продираться сквозь каталог типов данных.
Теперь, когда мы избавились от монотонности изучения каждого примитивного типа, давайте перейдем к более практическим вещам — отладке. Вместо того чтобы сразу приступать к анализу кода, давайте познакомимся с инструментами, которые предоставляет Zig, чтобы вы могли отслеживать, что происходит в ваших программах.
#
Базовая отладка
Итак, вы рисковали, работая с неопределенными переменными. Возможно, вы даже думали: «Эй, это просто неинициализировано, насколько это может быть плохо?». Ну, давайте просто предположим, что вы играете с огнем, но в режиме отладки Zig предоставляет вам огнетушитель.
Когда вы запускаете свой код в режиме отладки, Zig фиксирует использование вами undefined, заполняя его специальным байтовым шаблоном: 0xaa. Это не просто забава. Этот паттерн помогает понять, что вы работаете с неинициализированной памятью. Это как неоновая вывеска, которая говорит: «Эй, ты забыл присвоить значение этой переменной!»
Давайте посмотрим, как это работает.
Мы объявим переменную с помощью undefined и выведем ее значение. В режиме отладки Zig заполнит неинициализированную переменную специальным байтовым шаблоном 0xaa:
const print = @import("std").debug.print;
pub fn main() void {
var x: i32 = undefined; // Объявляем неинициализированную переменную
print("x = {}\n", .{x}); // Выводим значение неинициализированной переменной
}
Теперь давайте скомпилируем и запустим эту программу в режиме отладки.
#
Шаг 1. Сборка программы в режиме отладки
Поскольку режим отладки в Zig включен по умолчанию, просто соберите программу, как обычно:
$ zig build-exe your_program.zig
Zig позаботится о вас и автоматически заполнит неопределенные значения магическим байтовым шаблоном 0xaa. Вам не нужно включать режим отладки — он уже включен, и ваш код будет выглядеть немного аккуратнее. Так что расслабьтесь — никакого специального флага запоминать не нужно, вы в безопасности по умолчанию!
#
Шаг 2. Запуск программы
После сборки программы запустите ее и посмотрите, что произойдет:
$ ./your_program
x = -1431655766
#
Шаг 3. Что происходит?
Почему мы видим это странное значение? Главное здесь — понять, как работает память. Специальный байтовый шаблон 0xaa используется в Zig для заполнения неинициализированной памяти в режиме отладки. Этот шаблон соответствует шестнадцатеричному значению 0xaaaaaaaa. Если интерпретировать его как 32-битное целое число со знаком (i32), оно будет выглядеть как -1431655766. Если бы вы использовали другой тип переменной, результат был бы другим, но базовая память все равно была бы заполнена шаблоном 0xaa.
#
Разбираемся в байтовых шаблонах
Такое поведение — не просто какая-то странная особенность, оно призвано помочь вам быстро находить неинициализированные переменные. В режиме отладки Zig заполняет неинициализированную память значением 0xaa, чтобы вы сразу поняли, что забыли правильно инициализировать переменную. Это как дружеское напоминание.
А теперь можно и повеселиться. Поэкспериментируйте с разными типами, выведите их неопределённые значения и посмотрите, как Zig заполняет память для каждого из них. Вы быстро поймете, как эта небольшая функция помогает отслеживать неинициализированные переменные.
А теперь поэкспериментируйте — посмотрите, сколько типов можно нарушить с помощью undefined. Только помните: после всего этого хаоса не забудьте собрать все воедино — и, что еще важнее, понять, что именно пошло не так. Здесь в игру вступает отладка, и в Zig для этого есть специальный инструмент: std.debug.
#
std.debug
Пакет std.debug в языке Zig — это ваш незаменимый инструмент для отладки, призванный облегчить вам жизнь, когда что-то неизбежно идет не так. Считайте его своим набором инструментов для изучения того, что происходит «под капотом». Если вам когда-нибудь хотелось провести тщательный анализ кода, std.debug — это ваша лупа.
Но прежде чем мы углубимся в детали, важно отметить, что многие продвинутые функции в std.debug станут доступны вам чуть позже. Сейчас мы сосредоточимся на создании прочной основы. По мере вашего продвижения и усложнения проектов такие инструменты, как dumpCurrentStackTrace, attachSegfaultHandler и даже настраиваемые функции обработки паники, станут незаменимыми в вашем арсенале для отладки.
Пока что знайте, что std.debug обладает расширенными функциями отладки, включая захват трассировки стека, дамп памяти и обработку ошибок. Однако знакомство с этими функциями на ранних этапах может оказаться слишком сложным. Мы вернемся к ним, когда вы освоите основы языка Zig.
А пока вы будете часто сталкиваться с функцией std.debug.print() — простым, но эффективным способом проверки значений и получения обратной связи от кода в режиме реального времени. Это отличный инструмент для быстрого понимания того, что происходит в вашей программе, и по мере вашего продвижения вы, естественно, начнете осваивать более продвинутые функции. Но прежде чем мы перейдем к этому, давайте сосредоточимся на чем-то более фундаментальном: согласованности. Писать надежный и предсказуемый код гораздо проще, если придерживаться единого подхода — будь то присвоение имен переменным или структурирование программы. И хорошая новость в том, что чем организованнее ваш код, тем проще его отлаживать. Итак, давайте поговорим о важности последовательности и о том, как она помогает заложить прочный фундамент для всех ваших проектов на Zig.
#
Поддержание согласованности
Zig строго относится к кодировке: весь исходный код кодируется в UTF-8. Почему? Потому что стандарты существуют не просто так, и нам не нужны дикие, недопустимые последовательности байтов, которые портят наш код. Попробуйте добавить недопустимую последовательность UTF-8, и Zig выдаст ошибку компиляции быстрее, чем вы успеете произнести «неопределенное поведение».
Для чистоты кода в Zig есть несколько символов, которые не допускаются ни в коде, ни в комментариях:
- Управляющие символы ASCII: практически все они запрещены, кроме символов перевода строки (LF), возврата каретки (CR) и горизонтальной табуляции (HT). Таким образом, нельзя использовать символы от
U+0000доU+0008или что-то вродеU+007f. - Окончания строк в Юникоде, отличные от ASCII: необычные переносы строк в Юникоде, такие как "U+0085" (NEL), "U+2028" (LS) и "U+2029" (PS), запрещены. Делайте проще.
#
Окончания строк: делайте это проще, пусть это будет LF
В Zig в качестве окончания строки используется скромное LF. Это значение байта 0x0a (кодовая точка U+000a, или \n, если вам так удобнее). Каждая строка в исходном коде Zig должна заканчиваться символом LF, кроме последней. Для непустых файлов рекомендуется, чтобы файл заканчивался пустой строкой, то есть последним байтом должен быть символ LF.
Если вы привыкли работать с Windows, у вас может возникнуть соблазн использовать классическую комбинацию CR + LF (CRLF, или \r\n). Zig допускает использование этого символа, но не рекомендует. Если хотите, чтобы компилятор работал без ошибок, используйте LF. Кроме того, не пытайтесь использовать CR (0x0d, U+000d) где-либо еще, потому что он не используется, кроме как для формирования CRLF в конце строки.
#
Табуляция или пробелы: что выбрать (спойлер: используйте пробелы)
Несмотря на то, что в Zig в качестве разделителей токенов можно использовать табуляцию (\t, U+0009, 0x09), лучше использовать пробелы. Технически табуляция и пробелы взаимозаменяемы, но если вы не хотите развязать войну за форматирование, лучше использовать пробелы (0x20, U+0020).
#
Метки порядка следования байтов: только в начале
Если вам нужно добавить метку порядка следования байтов (BOM) в кодировке UTF-8, Zig проигнорирует ее, но только в том случае, если она находится в самом начале файла. Если вы поместите BOM в другое место, Zig не будет столь снисходителен.
#
Возможности zig fmt
Если от всех этих правил у вас голова идет кругом, не волнуйтесь. В Zig есть инструмент форматирования (zig fmt), который автоматически соблюдает все эти рекомендации. Просто запустите его, и ваш исходный код будет отформатирован идеально. Это как если бы у вас был личный уборщик кода, который вычищает все огрехи в кодировке:
$ zig fmt your_code.zig
Правила кодирования исходного кода в Zig разработаны таким образом, чтобы код был чистым, единообразным и удобным для работы. Используйте UTF-8, для окончания строк — символ перевода строки, а обо всем остальном позаботится zig fmt. Сохраняйте предсказуемость исходного кода, и все, включая компилятор, будут гораздо счастливее.
Теперь, когда вы вооружены этими знаниями, вы готовы узнать, как Zig использует эти концепции для создания проектов и управления ими. Система сборки — это не какой-то загадочный черный ящик, а просто код Zig в действии. Давайте приступим к изучению системы сборки.
#
Создание проектов на Zig
Итак, вы готовы создать свой первый проект на Zig, что немного сложнее, чем собрать один исходный файл. Не просто один исходный файл, а целый проект — добро пожаловать в высшую лигу. Прежде чем вы начнете паниковать из-за всех этих файлов и опций, давайте разберем процесс шаг за шагом. Поначалу структура проекта может показаться сложной, но на самом деле она проще, чем кажется.
#
Шаг 1. Создание проекта
Что ж, пришло время засучить рукава и создать свой первый проект на Zig. Не будем усложнять — просто создайте каталог, перейдите в него и позвольте Zig сделать всю тяжелую работу:
$ mkdir -p hw
$ cd hw
$ zig init
Если вам интересно, почему мы выбрали для папки название hw, то это сокращение от «Hello, World!». Но, эй, не стесняйтесь представлять, что это означает “Тяжелая работа”. В конце концов, каждый великий проект начинается с небольшого усердия, и это только начало вашего!
Бум! Зиг только что создал для вас четыре совершенно новых файла:
info: created build.zig
info: created build.zig.zon
info: created src/main.zig
info: created src/root.zig
Всё верно — Zig уже начинает чувствовать себя как дома.
#
Шаг 2. Ваша первая сборка
Теперь поговорим о сборке. Вы могли подумать, что Zig просто скомпилирует код, но нет — система сборки Zig структурирована и имеет множество опций. На самом деле она так и рвется показать себя во всей красе. Давайте посмотрим, что произойдет, если вы выполните следующую команду:
$ zig build --help
Zig завалит вас опциями — их так много! Но давайте не будем отвлекаться на все эти блестящие штучки. Пока что сосредоточимся на главном: как собрать программу.
Давайте посмотрим, что происходит, когда вы запускаете следующую команду:
$ zig build
Zig любезно создает для вас каталог zig-out/. Внутри вы найдете два подкаталога:
bin/: сюда помещается ваш статически скомпилированный двоичный файл.lib/: если вы собираете библиотеку, то здесь вы найдете файлы .a (набор файлов .o, объединенных в один)
Давайте посмотрим:
zig-out/
├── bin
│ └── hw # Ваш статически скомпилированный двоичный файл
└── lib
└── libhw.a # Статическая библиотека, если она у вас есть
#
Шаг 3. Запуск программы
Теперь, когда мы создали программу, давайте запустим ее:
$ ./zig-out/bin/hw
Поздравляем! Ваш первый проект на Zig запущен. Но постойте, что это за сообщение?
All your codebase are belong to us.
Run `zig build test` to run the tests.
Прежде чем вы начнете строить теории заговора: нет, команда Zig не взламывала ваш проект. Это просто стандартное сообщение для каждой только что созданной программы на Zig. Вы можете изменить его позже, но давайте пока насладимся этим.
#
Шаг 4. Что внутри исходного кода?
Давайте заглянем внутрь и посмотрим, какие файлы у вас есть:
.
├── build.zig
├── build.zig.zon
├── src
│ ├── main.zig
│ └── root.zig
└── zig-out
├── bin
│ └── hw
└── lib
└── libhw.a
#
Шаг 5. Слишком много компонентов
Я знаю, о чем вы думаете: «И все это ради простой программы?» Да, здесь есть кое-что для настройки, но у каждого компонента есть свое назначение. Прежде чем мы начнем что-то выбрасывать, давайте разберемся, что делает Zig:
build.zigотвечает за компиляцию вашего проекта, что очень важно для последующей настройки.build.zig.zonуправляет внешними зависимостями — если вы подключаете библиотеки, то именно здесь происходит волшебствоsrc/— это то место, где живет ваш код — это бьющееся сердце проектаzig-out/— это выходной каталог, в котором хранятся все результаты (двоичные файлы, библиотеки)
На данный момент это может показаться излишним, но поверьте мне, вы будете благодарны за такую структуру, когда ваш проект разрастется.
Теперь, когда вы создали и запустили свой первый проект на Zig, вам предстоит узнать еще много интересного. Система сборки Zig обладает широкими возможностями настройки, но мы рассмотрим их в следующих главах. Прежде чем двигаться дальше, важно закрепить свои знания об основах Zig, потому что все, что вы освоите в Zig, напрямую повлияет на саму систему сборки. Это похоже на бесконечный цикл совершенствования: чем больше вы изучаете Zig, тем лучше контролируете процесс сборки. Итак, давайте продолжим закладывать фундамент, прежде чем раскроем весь потенциал системы сборки.
А пока расслабьтесь и наслаждайтесь свежеиспеченным бинарным файлом. Вы это заслужили.
#
Итоги
Миссия выполнена! Вы успешно справились со своим первым опытом программирования на языке Zig и не просто напечатали «Hello, World!» и на этом успокоились. Нет, вы копнули глубже, разобравшись в основной функции, освоив возможности стандартной библиотеки и — не будем забывать, разобравшись в бунтарском подходе Zig к строкам (или, точнее, к их отсутствию). Вы даже научились работать с переменными так, как вам пригодится в будущем, и освоились с отладкой.
Помните, что const — это как друг, который всегда знает план, следует ему и никогда не устраивает скандалов. С другой стороны, var — это друг, который говорит: «Давайте импровизировать», а потом в три часа ночи в какой-нибудь глуши сбивает всех с пути. Выбирайте с умом.
Теперь вы знаете, что происходит за кулисами, когда вы используете zig run: Zig собирает исполняемый файл и запускает его, избавляя вас от лишних действий. Но если вам нужен больший контроль над процессом сборки, умение вручную компилировать и запускать код станет для вас ценным навыком.
Далее мы перейдем к управлению потоком выполнения, где происходит принятие реальных решений. Мы рассмотрим условные операторы, циклы и то, как Zig позволяет направлять программы именно в том направлении, которое вам нужно. Это следующий логичный шаг на пути к освоению Zig и полному контролю над потоком кода.
#
Закрепите пройденный материал
Теперь, когда мы рассмотрели основные понятия, пришло время закрепить полученные знания. Давайте проверим, насколько хорошо вы усвоили эти идеи! Следующие вопросы помогут вам закрепить ключевые моменты из этой главы. Не волнуйтесь, это не экзамен, а возможность поразмышлять и лучше усвоить материал. Так что налейте себе кофе, сделайте глубокий вдох и приступим к упражнениям!
Основная функция
Вопрос: что означаетpub fn main() void { ... }в языке Zig?- Частная функция без возвращаемого значения
- Публичная функция, возвращающая строку
- Публичная функция, которая не принимает аргументов и ничего не возвращает
- Публичная функция, импортирующая стандартную библиотеку
Запуск программы
Вопрос: что происходит после создания файла main.zig, когда вы запускаете$ zig run main.
zig без дополнительного кода внутри функции main?- Программа выводит «Hello, World!»
- Программа ничего не делает, но ошибок нет
- Программа выдает ошибку компиляции
- Программа выводит стандартное сообщение компилятора Zig
Импорт стандартной библиотеки
Вопрос: что делает в Zig строка const std = @import("std");?- Она импортирует стандартную библиотеку и присваивает ее константе std.
- Она определяет новую стандартную библиотеку для вашего проекта.
- Она объявляет функцию в стандартной библиотеке.
- Она импортирует внешние зависимости по пользовательскому пути.
Вывод на экран
Вопрос: Как правильно вывести «Привет, Зиг!» в Zig с помощью стандартной библиотеки?- std.debug.print("Привет, Зиг! \n", );
- std.print("Привет, Зиг!", );
- std.debug.print("Привет, Зиг! \n", .);
- std.debug.print("Привет, Зиг!", .);
Встроенная функция @import
Вопрос: Для чего в Zig используется встроенная функция @import?- Она загружает файл Zig и возвращает соответствующую структуру.
- Она запускает компилятор Zig.
- Она определяет срез в памяти.
- Она импортирует внешние библиотеки.
Подход Zig к работе со строками
Вопрос: Как Zig обрабатывает текст вместо использования традиционных строк?- Используются фрагменты байтов
- Используются указатели на символы
- Используются массивы строк
- Используются строковые буферы
Объявление переменных:
constиvar
Вопрос: Что из перечисленного верно о ключевом словеconstв языке Zig?- Оно позволяет изменять значение после инициализации
- Оно объявляет переменную, которую нельзя изменить после инициализации
- Объявляет функцию, которая не может возвращать значение
- Используется для объявления строк в Zig
Использование var в Zig
Вопрос: Когда следует использовать ключевое слово var в Zig?- Когда вы хотите, чтобы переменная была неизменяемой
- Когда вы хотите, чтобы переменная была изменяемой и менялась с течением времени
- Когда вы хотите импортировать стандартную библиотеку
- Когда вы хотите объявить постоянное значение
Неинициализированные переменные в Zig
Вопрос: Что произойдет, если вы объявите переменную в Zig, но не инициализируете ее?- Zig автоматически инициализирует ее значением 0
- Программа завершает работу с ошибкой
- Программа не будет скомпилирована из-за ошибки
- Переменная инициализирована специальным неопределенным значением
Отладка с неинициализированными переменными
Вопрос: Какое значение присваивается переменной со значениемundefinedв Zig в режиме отладки?- 0
- 1
- Специальный байтовый шаблон
0xaa - Адрес в памяти
Сборка исполняемых файлов
Вопрос: Что делает командаzig build-exe src/main.zig?- Запускает программу Zig напрямую
- Собирает исполняемый файл вручную из исходного кода
- Создает новый каталог проекта
- Создает файл библиотеки
Выходные файлы после сборки
Вопрос: Какие файлы создаются после запуска командыzig build-exe?- Только основной исполняемый файл
- Исполняемый файл и текстовый лог-файл
- Исполняемый файл и объектный файл (main.o)
- Исполняемый файл и стандартный текстовый файл с выводом
Затенение в Zig
Вопрос: что произойдет, если в Zig попытаться объявить две переменные с одинаковыми именами во вложенной области видимости?- Zig автоматически использует внутреннюю переменную и игнорирует внешнюю.
- Zig выдаст ошибку во время компиляции из-за запрета затенения.
- Обе переменные будут доступны.
- Zig заменит внешнюю переменную внутренней.
Инициализация проекта
Вопрос: что делает команда zig init при создании нового каталога проекта?- Она создает один файл main.zig
- Она создает несколько файлов, в том числе build.zig и src/main.zig
- Она собирает проект и запускает исполняемый файл
- Она компилирует тестовую программу
Сборка и запуск программ вручную
Вопрос: В чем преимущество использования командыzig build-exeвручную по сравнению сzig run?- Это быстрее, чем zig run
- Это дает больше возможностей для контроля процесса компиляции, что полезно для сложных проектов
- Это нужно только для отладки
- Это позволяет избежать создания объектных файлов
Работа с объектными файлами
Вопрос: Что представляет собой файл main.o после запуска команды zig build-exe?- Скомпилированный исходный код в машиночитаемом формате
- Исполняемый файл
- Исходный код с отладочной информацией
- Файл настроек проекта
Отладка памяти
Вопрос: Какой специальный байтовый шаблон использует Zig в режиме отладки для обнаружения неинициализированной памяти?- 0xbb
- 0xff
- 0xaa
- 0x00
Обязательная инициализация переменных
Вопрос: Почему в Zig требуется инициализация всех переменных?- Чтобы ускорить работу программы
- Чтобы избежать использования неопределенных или «мусорных» значений в памяти
- Чтобы сделать переменные изменяемыми
- Для компиляции программы в режиме отладки
Стандартные библиотечные инструменты для отладки
Вопрос: Для чего в Zig используется функция std.debug.print()?- Она выводит адреса памяти
- Она позволяет выводить отформатированные строки для отладки программы
- Она автоматически выводит трассировку стека
- Она запускает тестовую программу в режиме отладки
Правила затенения переменных
Вопрос: Почему Zig запрещает затенение переменных?- Чтобы избежать двусмысленности и потенциальных ошибок, связанных с повторным использованием имен переменных во вложенных областях видимости
- Чтобы ускорить выделение памяти
- Чтобы можно было использовать одну и ту же переменную в нескольких областях видимости
- Чтобы повысить производительность программы
#
Ответы
- c
- b
- a
- c
- a
- a
- b
- b
- c
- c
- b
- c
- b
- b
- b
- a
- c
- b
- b
- a