Введение в ZIG

2025-02-20 4399 21

Это вводная глава по языку Zig. В ней мы рассмотрим, что такое язык Zig и откуда он появился, чем он уникален, и сравним его немного с другими языками. Также мы настроим среду для разработки Zig-приложений, немного взглянем на процесс сборки и соберем простую программу.

Появление Zig

Zig — это системный язык программирования. Он начал свою историю в 2015 году, когда его автор, Эндрю Келли, согласно легенде, захотел написать хороший музыкальный плеер. Попробовав сделать это на Node.js, Rust и C++, он в конечном итоге пришёл к разработке собственного языка.

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

Долгое время Zig оставался в тени, и, вероятно, только в 2023 году его популярность начала расти, а на него стали обращать внимание. Это произошло отчасти потому, что язык набрал «критическую массу» и уже позволял решать многие задачи, а также потому, что некоторые компании начали использовать его в своих проектах, дав программистам понять, что он действительно заслуживает интереса.

Итак, что же такое Zig, и зачем нам ещё один язык, когда у нас уже есть C, C++, Java и Go? К сожалению, многие языки, такие как C++ и Java, за годы своего существования стали слишком сложными. Порог входа в них значительно повысился, а высококлассных специалистов, знающих все тонкости, на рынке оказалось меньше, чем требовалось, что привело к дефициту разработчиков. Быстро обучать новых специалистов таким языкам — довольно сложная задача, поэтому многие пытались решать проблему кадров другими способами.

Например, компания Google создала Go — очень простой язык, ориентированный на решение конкретных задач, с которыми часто сталкивались внутри компании, но при этом испытывали нехватку разработчиков. Сообщество Rust, в свою очередь, сфокусировалось на создании безопасного языка, который не позволял бы разработчику совершать критические ошибки и делал программы более надёжными. Однако подход Rust показал, что язык получился не намного проще C++ или Java, и многие так и не смогли его освоить из-за всё ещё высокого порога входа.

Авторы языка Zig пошли по пути языка Go - они хотели сделать простой, надежный и производительный язык. Чтобы сделать язык понятным и удобным, Вам придется чем-то пожертвовать, и Zig тут не исключение. В этом языке многие вещи были исключены, так как он разрабатывался для решения конкретных задач. Хотя многие считают, что сейчас его можно использовать и для широкого круга задач, он все еще не является универсальным языком программирования и некоторые вещи проще реализовывать на Go, чем на Zig.

Философия Zig

Давайте рассмотрим, что же было заложено в философию Zig его автором и поддерживается теперь сообществом:

Простота

Автор языка Zig старался сделать его простым и не добавлять в него сложных конструкций, которые бы усложняли чтение кода. Например, долгое время в Zig не было цикла for — считалось, что цикла while вполне достаточно для выполнения любых задач. В Zig нет скрытого потока управления, макросов и препроцессора. Zig гарантирует, что поток выполнения является явным и прозрачным. В отличие от C++, Zig не поддерживает перегрузку операторов, неявные преобразования или исключения, которые могут вызывать скрытые переходы в потоке управления.

Этот принцип проектирования улучшает читаемость, предсказуемость и простоту отладки. “Код, который ты видишь, ты и получаешь” — именно такой мыслью вдохновлялся автор языка. Да, в Zig есть дополнительный режим выполнения кода — который выполняется во время компиляции, но это довольно простой инструмент, в отличие от макросов в Rust, например. Когда вы стараетесь сделать простой язык программирования, у вас есть два пути: либо вы просто исключаете возможности из языка, либо переносите всю сложность в рантайм языка. Второй путь выбрал Google при создании Go, в Zig же автор пошел по первому пути, просто исключив многое из языка, так как создание сложного рантайма нарушало бы другие принципы языка.

Иногда, наткнувшись на отсутствие чего-то привычного, вы впадаете в ступор и зависаете с вопросом «а как?», например, не найдя в языке привычного механизма интерфейсов. Конечно, это потребует от разработчика перезагрузить свой «ментальный генератор» кода и настроить его под язык. В целом авторы языка придерживаются идеи, что для каждой задачи должен быть только один очевидный способ решения. Я согласен с таким подходом, а также с тем, что язык должен загружаться в голову разработчика за пару дней и удерживаться там минимальными усилиями. Удачное это решение и сможет ли сообщество языка держать тот же курс в простоте — покажет время, но в целом этот принцип довольно хорош и показал свою эффективность при появлении языка Go.

Надежность

Проектируя язык, автор и сообщество стараются не забывать о том, что в наше время язык должен быть надежным, но делают это немного по-своему. Например, если Rust просто не дает разработчику вляпаться в «простые» баги, которые легко не заметить, если у вас большая кодовая база, то Zig встраивает надежность только если это не противоречит остальным принципам языка, таким как «Простота» и «Оптимальность».

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

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

В Zig также уделили внимание обработке ошибок и, вдохновившись, вероятно, такими языками, как Go и Rust, реализовали её без использования механизма исключений. Обработка ошибок в Zig во многом похожа на Rust, но с одним важным отличием — она упрощена. Если вы попробуете разобраться в обработке ошибок в Rust и введёте соответствующий запрос в Google, то найдёте множество статей о том, как правильно это делать, зачастую с использованием сторонних библиотек. В Zig же, благодаря простоте языка, вам понадобится совсем немного времени, чтобы освоить обработку ошибок и применить её в своей программе.

Производительность

Язык Zig — это системный язык программирования, который позволяет писать код, работающий близко к железу и операционной системе. Если для вас критична производительность, важно точно контролировать результат компиляции и понимать, во что ваш код превращается на уровне машинных инструкций. Кроме того, Zig даёт возможность полностью управлять потоком выполнения программы, исключая влияние рантайма и скрытых эффектов, присущих другим языкам.

Zig был спроектирован так, чтобы не скрывать от разработчика деталей работы и не приводить к неоптимальному коду после компиляции. Язык поддерживает как связывание с libc, так и работу без неё, что делает его удобным как для программирования на «голом железе», так и для высокопроизводительной разработки. В этом смысле Zig во многом похож на C, и его нередко называют «C на стероидах». Если для вас важны производительность и предсказуемость кода, Zig предоставляет всё необходимое — и даже больше — для написания высокоэффективных программ.

Оптимальность

Оптимальность подразумевает написание кода, который наилучшим образом использует ресурсы, такие как ЦП и память. Язык Zig старается дать разработчику инструменты, которые позволяют писать наиболее оптимальный код. У вас есть полный контроль над вашим кодом — никаких скрытых затрат, никаких «закулисных» обработок кода, никаких скрытых выделений памяти. Язык старается не приносить никаких решений только потому, что это есть где-то еще.

Добавление полноценной поддержки асинхронного кода в Zig было приостановлено. Автор языка объяснил это тем, что на данный момент нет чёткого понимания, как реализовать async, не нарушая фундаментальные принципы Zig. Внедрять асинхронность только потому, что она есть в других языках, — не лучшая идея. Тем не менее, вероятность появления поддержки асинхронного программирования в будущем остаётся высокой. Асинхронный код — это популярная концепция, активно используемая в современных языках, особенно в разработке высоконагруженных и сетевых приложений. Оставлять её без внимания вряд ли разумно.

При этом радует сам подход разработчиков Zig: они не гонятся за трендами и не добавляют функциональность ради формального соответствия модным тенденциям. Вместо этого они тщательно анализируют, насколько та или иная возможность действительно нужна, и как её можно реализовать наиболее эффективно и согласованно с идеологией языка. Такой методичный подход даёт надежду, что, когда асинхронность всё же появится в Zig, она будет продумана и удобна в использовании, а не просто «для галочки».

Читаемость

Многим разработчикам давно известно, что мы читаем код гораздо больше времени, чем пишем. И чем проще процесс чтения делает нам язык программирования, тем легче нам поддерживать кодовую базу. Есть довольно большое количество языков, где можно написать очень эстетичный и компактный код, но по прошествии времени вы потратите много усилий, пытаясь понять его и вспомнить, что же он делает. Разработчики Zig стремились создать такой стиль кодирования, при котором основное внимание уделяется логике программы, а не борьбе с особенностями синтаксиса или запутанными паттернами. Читая код на Zig, разработчик должен тратить время на понимание самой программы, а не на вспоминание специфических конструкций языка или сложных способов обойти его ограничения.

Одним из примеров можно назвать отличие Zig от Rust в вопросе контроля кода. В Rust строгая система владения и заимствования памяти иногда вынуждает программиста усложнять код, чтобы соответствовать требованиям компилятора. В некоторых случаях приходится добавлять лишние конструкции, даже если с точки зрения бизнес-логики программа может выполняться только одним возможным образом, а альтернативные пути просто невозможны. Однако компилятор Rust об этом не знает и требует дополнительных гарантий безопасности, что может привести к избыточным усложнениям.

В Zig подход более прагматичный — язык даёт разработчику больше контроля и не навязывает чрезмерные ограничения. Это позволяет писать предсказуемый, понятный и при этом эффективный код без необходимости «переигрывать» компилятор. Такой баланс делает Zig привлекательным выбором для тех, кто хочет работать с низкоуровневым кодом, но при этом не сталкиваться с излишней сложностью.

Уникальные возможности zig

Полная совместимость с C

Язык Zig поддерживает полную совместимость с C, что позволяет включать код на C в Zig-программу и компилировать его без лишних сложностей. Это одно из ключевых преимуществ языка, особенно в его ранних этапах развития, когда экосистема ещё не успела обзавестись достаточным количеством собственных библиотек и инструментов.

Одной из главных проблем новых языков программирования является нехватка готовых решений. Когда разработчик начинает писать что-то на свежем языке, он часто сталкивается с тем, что многие привычные задачи, которые в других языках решаются парой команд и подключением готовой библиотеки, приходится реализовывать с нуля. Причём если программа должна работать на нескольких платформах, то помимо написания собственного решения необходимо позаботиться о кроссплатформенности. Это требует времени и усилий, что может замедлить процесс разработки.

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

Выполнение кода во время компиляции (Comptime)

Выполнение кода во время компиляции в Zig — не революционная идея, но крайне мощный инструмент. Впервые концепция вычислений на этапе компиляции появилась ещё в 1960-х годах в языке Lisp. Однако среди современных статически типизированных языков программирования такая возможность встречается нечасто.

Некоторые элементы компиляционного вычисления можно увидеть в C++, где оно реализовано через шаблоны (templates). Однако этот механизм сложно назвать удобным — он усложняет код, делает его менее читаемым и может значительно увеличивать время компиляции. В Zig же выполнение кода во время компиляции (comptime) организовано более элегантно и предсказуемо.

Эта особенность позволяет писать универсальный и эффективный код, устраняя необходимость в сложных конструкциях для метапрограммирования. В отличие от шаблонов C++, comptime в Zig даёт программисту простой и понятный способ управлять выполнением кода на этапе компиляции. Это снижает сложность, улучшает читаемость и предсказуемость программ, а также открывает дополнительные возможности для оптимизации.

Рассмотрим простой пример, демонстрирующий использование comptime в Zig. Предположим, у нас есть функция, вычисляющая максимум двух чисел:

fn maximum(a: i32, b: i32) i32 {
    var result: i32 = undefined;

    if (a > b) {
        result = a;
    } else {
        result = b;
    }

    return result;
}

Очевидно, что это решение ограничено и поддерживает только 32-битные числа. Если бы мы писали на C, нам пришлось бы использовать макросы препроцессора, чтобы сделать код универсальным. Однако создатели Zig не хотели заимствовать эту не самую удачную часть C.

Во многих языках, поддерживающих шаблонный (generic) код, такую задачу можно решить достаточно просто, используя универсальные шаблоны для типов данных. Zig предлагает аналогичное решение, но с гораздо большей гибкостью. В отличие от других языков, где шаблонный код ограничен только работой с типами, в Zig возможность выполнения кода на этапе компиляции (так называемое comptime) открывает гораздо более широкий спектр возможностей. Это позволяет использовать один и тот же инструмент для решения различных задач, включая, но не ограничиваясь, созданием универсального кода.

Вместо того чтобы изучать и осваивать многочисленные особенности и синтаксические конструкции для каждой новой задачи (как это происходит, например, с шаблонами в C++), в Zig вы можете использовать мощный и простой механизм comptime для решения различных проблем. Этот подход позволяет разрабатывать более читаемые и понятные программы, где единый инструмент решает задачи, связанные с генерацией кода, метапрограммированием и даже оптимизацией на этапе компиляции.

Кроме того, использование comptime в Zig упрощает код, избавляя от необходимости использовать сложные или тяжёлые механизмы, такие как макросы препроцессора или шаблоны, как в C и C++. Это делает язык более прозрачным и удобным для разработчиков, позволяя им сосредоточиться на логике программы, а не на изучении дополнительных инструментов. Давайте рассмотрим решение нашей проблемы на Zig:

fn maximum(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

Здесь мы говорим компилятору, что тип нашей переменной будет известен во время компиляции (указав параметр comptime), и компилятор просто сгенерирует нужный набор функций для различных типов. Это примерно похоже на то, как делает Rust, который тоже по вашему шаблонному коду сгенерирует функции под разные типы данных, но, как я уже сказал, в Zig вы можете использовать этот подход и для решения других задач. Например, в нашей функции сейчас нет проверки, что в нее передали только те типы данных, которые поддерживают сравнения. Давайте добавим это в наш код:

fn assertNumber(comptime T: type) void {
    const is_num = switch (T) {
        i8, i16, i32, i64 => true,
        u8, u16, u32, u64 => true,
        comptime_int, comptime_float => true,
        f16, f32, f64 => true,
        else => false,
    };

    if (!is_num) {
        @compileError("Inputs must be numbers");
    }
}

fn maximum(comptime T: type, a: T, b: T) T {
    const A = @TypeOf(a);
    const B = @TypeOf(b);

    assertNumber(A);
    assertNumber(B);

    if (A != B) {
        @compileError("Inputs must be of the same type");
    }

    return if (a > b) a else b;
}

Выполнение кода во время компиляции — это достаточно простой, но мощный механизм, хотя у него есть свои ограничения. Например, вы не можете взаимодействовать с пользовательским вводом, читать файлы или выполнять сетевые запросы на этапе компиляции. Однако в целом текущих возможностей вполне достаточно для решения множества задач, которые в других языках либо выглядят сложнее (например, макросы в Rust), либо встроены несколько необычным образом, как это сделано в Go с конструкцией generate.

Кросс-компиляция

В современном мире существует большое разнообразие операционных систем, и когда мы выпускаем наш продукт, мы, естественно, хотим, чтобы он работал на всех возможных платформах. Однако кросс-компиляция кода далеко не всегда проходит гладко, и порой требуется потратить много времени и усилий, чтобы добиться нужного результата. Например, те, кто пытался скомпилировать код для трёх основных платформ с использованием Rust, знают, что этот процесс не так уж прост. Даже если ваш код успешно скомпилировался, это ещё не гарантирует, что он будет работать. Проблема может быть связана с тем, что вы скомпилировали его с GLIBC, установленной на вашей машине, но на целевой платформе версия GLIBC может быть более старой, из-за чего код просто не запустится.

В отличие от этого, в Zig кросс-компиляция работает из коробки, что значительно упрощает процесс разработки. Более того, вы можете указать версию GLIBC, с которой нужно слинковать ваш код, даже если она не установлена на вашей машине. Это значительно упрощает сборку программы на одной машине для различных платформ. Такое решение полностью соответствует основному принципу Zig: «Сосредоточьтесь на отладке вашего приложения, а не на проверке своих знаний о языке программирования».

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

Никаких скрытых выделений памяти

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

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

Для удобства работы с памятью Zig предоставляет несколько различных типов аллокаторов, которые могут быть использованы для разных задач. Каждый из этих аллокаторов подходит для определённых ситуаций, будь то выделение памяти для временных объектов, для больших данных или для многозадачности. Zig также упрощает работу с памятью с помощью конструкций defer и errdefer. Эти механизмы позволяют автоматически управлять освобождением памяти, предотвращая утечки и снижая вероятность ошибок при управлении ресурсами. Например, конструкция defer гарантирует, что выделенная память будет освобождена, когда выполнение программы покинет текущую область видимости, что упрощает управление ресурсами и делает код более безопасным и читаемым.

Таким образом, Zig сочетает в себе высокий уровень контроля над памятью и инструменты, упрощающие её управление, что делает язык отличным выбором для разработчиков, работающих в высокопроизводительных и чувствительных к ресурсам средах.

Области применения

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

Если вы разрабатываете продукт в одной из этих областей Вам точно стоит присмотреться к zig.

Установка

Давайте наконец установим Zig на машину. Установка Zig довольно проста — вам просто нужно скачать версию Zig с сайта https://ziglang.org/ для вашей ОС, распаковать архив в папку, и все. Добавив папку из распакованного архива в вашу переменную PATH вы сможете использовать все возможности Zig. Для того чтобы проверить что все установилось как надо выполните комаду zig version:

$ zig version
0.14.0

Если Вы используете VS Code в качестве IDE для разработки, то для работы с Zig Вам надо установить два расширения - Zig Language и CodeLLDB. Первое даст Вам правильную работу с кодом на Zig, а второе позволит отлаживать код.

Компиляция тестового кода

Итак, давайте напишем первый код. Для этого создайте директорию, перейдите в нее и выполните команду zig init:

$ mkdir simple
$ cd simple
$ zig init

info: created build.zig
info: created build.zig.zon
info: created src/main.zig
info: created src/root.zig
info: see `zig build --help` for a menu of options

Как мы видим, компилятор создал несколько файлов в нашей директории. Давайте рассмотрим содержимое директории. Первое, что бросается в глаза, — это расширение файлов .zig. Файлы с кодом на языке Zig всегда имеют это расширение. Если в имени файла используется несколько слов, их обычно разделяют с помощью нижнего подчеркивания для удобства чтения.

Если ваша цель — написать исполняемую программу, то по соглашению файл main.zig содержит код вашей основной функции main(), которая является точкой входа в программу, то есть именно с нее начинается выполнение кода. Если вы разрабатываете библиотеку, стандартная практика — удалить файл main.zig и начать с модуля root.zig. По соглашению, root.zig является основным исходным файлом вашей библиотеки.

Также при выполнении команды init были созданы еще два файла: build.zig и build.zig.zon. Они необходимы для установки дополнительных пакетов и сборки нашего приложения, но их мы рассмотрим позднее, а пока не будем заглядывать в них. Итак, давайте посмотрим, что же внутри файла root.zig:

const std = @import("std");
const testing = std.testing;

export fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "basic add functionality" {
    try testing.expect(add(3, 7) == 10);
}

Здесь мы видим, что наш код начинается с импортирования стандартной библиотеки Zig. Вряд ли вы сможете написать полезное приложение, не импортировав ничего из стандартной библиотеки. Из нее нам необходим набор функций для тестирования, так как команда init также сгенерировала нам тесты для нашей простой функции. В целом синтаксис import очень схож с тем, как это устроено в других языках, таких как JavaScript или Go.

Далее мы видим функцию add, синтаксис которой очень похож на синтаксис языка Rust — мы используем fn для определения функции и определяем у нее два параметра с типом i32. Функция также вернет нам значение с типом i32 в результате своей работы. Так как Zig — строго типизированный язык, нам почти всегда будет необходимо указывать типы передаваемых значений.

Также мы можем заметить ключевое слово export, стоящее перед нашей функцией. Оно работает примерно так же, как extern в C, и говорит компилятору, что функция будет доступна наружу из нашей библиотеки.

Теперь давайте рассмотрим код нашего основного файла:

//! By convention, main.zig is where your main function lives in the case that
//! you are building an executable. If you are making a library, the convention
//! is to delete this file and start with root.zig instead.

pub fn main() !void {
    // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
    std.debug.print("All your {s} are belong to us.\n", .{"codebase"});

    // stdout is for the actual output of your application, for example if you
    // are implementing gzip, then only the compressed bytes should be sent to
    // stdout, not any debugging messages.
    const stdout_file = std.io.getStdOut().writer();
    var bw = std.io.bufferedWriter(stdout_file);
    const stdout = bw.writer();

    try stdout.print("Run `zig build test` to run the tests.\n", .{});

    try bw.flush(); // Don't forget to flush!
}

test "simple test" {
    var list = std.ArrayList(i32).init(std.testing.allocator);
    defer list.deinit(); // Try commenting this out and see if zig detects the memory leak!
    try list.append(42);
    try std.testing.expectEqual(@as(i32, 42), list.pop());
}

test "use other module" {
    try std.testing.expectEqual(@as(i32, 150), lib.add(100, 50));
}

test "fuzz example" {
    const Context = struct {
        fn testOne(context: @This(), input: []const u8) anyerror!void {
            _ = context;
            // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case!
            try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input));
        }
    };
    try std.testing.fuzz(Context{}, Context.testOne, .{});
}

const std = @import("std");

/// This imports the separate module containing `root.zig`. Take a look in `build.zig` for details.
const lib = @import("simple_lib");

Если мы взглянем на нашу функцию main, то первое, что бросится нам в глаза, — это странный тип возвращаемого значения. Тип void говорит компилятору, что наша функция не возвращает значения, но что за непонятный восклицательный знак в начале? Таким образом, в Zig указывается, что функция может возвращать ошибку. Полный синтаксис выглядит так: SomeError!void, но если мы опустим указание типа ошибки, то Zig выведет тип за нас на основе тех ошибок, что встретит в коде нашей программы.

Если мы посмотрим код нашей функции, то неясно, а как же мы возвращаем эту самую ошибку. И тут в работу вступает ключевое слово try. Хоть оно и похоже на привычные нам try-catch, но работает это в Zig немного по-другому. В нашем примере мы видим использование try при вызове записи в stdout и при вызове flush. Если во время выполнения этих функций произойдет ошибка, то она автоматически будет возвращена из нашей функции. Если ошибки не будет, то конструкция try просто вернет значение, как будто ее и не было.

Последнее, на что мы обратим внимание в нашей функции main, — это ключевое слово pub. По умолчанию все функции модуля в Zig приватные и недоступны вне модуля. Чтобы вызывающий код мог использовать вашу функцию, вам нужно пометить ее ключевым словом pub.

Итак, давайте наконец скомпилируем и запустим нашу простую программу. Для этого выполним команду:

zig build

В результате выполнения этой команды в папке нашей программы появится директория zig-out с двумя поддиректориями:

./zig-out
├── bin
│   └── simple
└── lib
    └── libsimple.a

3 directories, 2 files

Файл libsimple.a в директории lib — это наша статическая библиотека, которую мы линкуем с программой. Расширение .a как раз указывает на то, что это архив, содержащий объектные файлы, которые будут статически подключены к нашему приложению. В директории bin находится исполняемый файл нашей программы. Давайте попробуем запустить его:

$ ./zig-out/bin/simple
All your codebase are belong to us.
Run `zig build test` to run the tests.

Мы видим, что наша программа успешно выполнилась и вывела на экран то, что мы видели, когда смотрели код программы. Давайте теперь запустим наши тесты, выполнив команду zig build test --summary all.

$ zig build test --summary all
Build Summary: 5/5 steps succeeded; 4/4 tests passed
test success
├─ run test 1 passed 432ms MaxRSS:1M
│  └─ zig test Debug native success 1s MaxRSS:245M
└─ run test 3 passed 642ms MaxRSS:1M
   └─ zig test Debug native success 1s MaxRSS:249M

Параметр --summary all нужен чтобы увидеть вывод тестов, так как по умолчанию если тесты прошли успешно, то Zig ничего не выведет на экран.

Заключение

Резюмируя все то, что мы узнали о языке Zig из первой главы, можно снова задаться вопросом: зачем нам еще один C-подобный язык? Ответ прост. Если Zig удастся снизить эксплуатационные расходы, улучшая опыт разработчиков, безопасность, качество и время выхода на рынок новых функций, то компаниям, пишущим на C/C++/Rust, придется конкурировать с теми, кто использует Zig. И если они не будут этого делать, то начнут проигрывать эту гонку и терять своих клиентов.

Конечно, Zig пока еще далек от версии 1.0, и часто с выходом новой версии ваш код может сломаться. Также, если вы будете искать примеры кода в интернете или в GPT, то вероятно, что вы найдете уже неработающие примеры, так как язык сейчас активно меняется нередко что-то удаляется из языка, а что-то добавляется. Но язык уже набрал критическую массу, и я думаю, что выход первой версии уже не так далек, как в 2016 году.

#go #zig #zigbook

0%