Pull to refresh

Нововведения Zig версии 0.12.0, которые стоит упомянуть

Reading time12 min
Views6.3K

Вводное слово

По случаю выхода версии 0.12.0 языка Zig я снова решил написать статью об этом языке. И в этот раз о том, что изменилось в языке относительно версии 0.11.0. Сразу уточню, что я не буду описывать все изменения. Ссылка на примечания к выпуску под цифрой выше. Список изменений хоть и не такой большой, как у версии 0.11.0, не все изменения мне интересны. Я опишу только те, что мне показались более важными для языка. Да, и если уж делать полный обзор, нужно давать подробные примеры - более детальные, чем те, что предложены в примечаниях к выпускам. Потому что не все изменения будут понятны сразу, нужно сделать сравнение. Это не претензия к авторам, просто есть детали, которые понятны только на более конкретных примерах (и если прочитать между строк, то это я нифига не понял).

Начну я с цитаты из примечания к выпуску

This release features 8 months of work: changes from 268 different contributors, spread among 3688 commits.

Стоит здесь выделить не время потраченное на работу и не количество коммитов. А количество контрибьютеров. 268 человек это достаточно много. С моей точки зрения такая цифра показывает интерес к языку. Ведь это не просто звёздочки на GitHub, а полноценный вклад в разработку языка программирования. Я даже могу указать, что среди русско-говорящего сообщества есть представители, что внесли свой вклад в развитие языка. Если они захотят, то отметятся в комментариях, так как они есть среди хабравчан. К своему стыду, я не в хожу в списки контрибуторов. Я один из тех, кто больше читал, чем писал. 8 месяцев факапов. фото_гарольда, скрывающего_боль.jpg

В общем, сейчас об изменениях коротко списком:

  • Новый генератор документации из комментариев в коде

  • Изменения в языке

  • Изменения в стандартной библиотеке

  • (Не)Новый линкер

  • Изменения в системе сборки

  • Изменения в системе кэшей

Внимание! Стоит сразу обозначить, что язык Zig всё ещё не готов для полноценного использования в коммерческом коде. Он до сих пор меняется. И версия 0.12.0 не исключение. Есть обозначенные временные вехи и версии, когда будут добавлены те или иные новые возможности языка. Но уже есть проекты, которые используют Zig в полной мере. И никто не запрещает использовать этот язык для своих экспериментов.

Внимание 2! Документация для языка всё еще не полная. В ней есть практически всё. Но что-то осталось не полностью описано. Что-то вовсе не имеет описания. Если вас интересует какие-то вопросы, пишите в комментариях, или обращайтесь с ними в любое сообщество по языку. Ссылки я также указал в конце статьи.

Ссылка на примечания к выпуску версии 0.12.0. Дублирую.

Что поменялось?

1. Генератор документации из комментариев в коде

По описанию переделали весь встроенный генератор. Уточняю, чтобы не было разночтений. В репозитории языка Zig находятся два генератора. Один генерирует документацию по языку, второй генерирует документацию по стандартной библиотеке. Речь сейчас идёт о втором генераторе, который по сути генерирует документацию из текста комментариев в исходном коде. Первый генератор строит документацию из готовых файлов. Деталей данного механизма я не знаю.

По сути выкинули старый, и внедрили новый. Новый построен на том, что компилируется в wasm или WebAssembly по другому. Что с моей стороны выглядит двояко, но в этом есть зерно логики. Так как теперь можно поднять простой сервер локально, и просмотреть документацию без необходимости выходить куда-то в интернет. Плюс ещё в том, что теперь не нужны дополнительные зависимости стандартные для фронтенда, типа php, и его шаблонизаторов, или react, и вот это вот всего. Всю динамическую логику фронтенда теперь можно писать на zig. При этом также уменьшилось время компиляции документации, уменьшился размер конечного результата. Это если сравнивать генерацию документации для стандартной библиотеки языка. То есть плюсы есть и немалые. И даже указывается, что приставку «экспериментальный» у генератора убрали. Для разработчиков он теперь полноценный инструмент, что похвально.

[Ниже я бомблю mode on]

Я на самом деле не совсем понимал, что происходило с генератором начиная с версии 0.9.0, с которой я начал свой знакомство с языком Zig. Со своей стороны я видел какую-то странную картину. Ещё в версии 0.9.0 была возможность компилировать документацию из скрипта системы сборки build.zig. Пример кода:

const docs = b.addTest(.{
    .root_source_file = .{ .path = "src/main.zig" },
    .optimize = optimize,
});
docs.emit_docs = .emit;

const docs_step = b.step("docs", "Generate docs");
docs_step.dependOn(&docs.step);

Выполнив команду zig build docs можно было получить готовую документацию.

Этот же код работал в версии 0.10.0. Но уже в версии 0.11.0 этот механизм перестал работать. Его полностью удалили. Заменив на другой, но официально об этом не указали ни в документации, ни в примечаниях к релизу. И потому я лично был удивлён, что в примечаниях к выпуску информация о генераторе документации есть, а самого рабочего генератора как такового нет.

Оказалось, что для генерации документации нужен такой код (пример из файла build.zig репозитория языка Zig на GitHub):

const autodoc_test = b.addObject(.{
    .name = "std",
    .root_source_file = b.path("lib/std/std.zig"),
    .target = target,
    .zig_lib_dir = b.path("lib"),
    .optimize = .Debug,
});
const install_std_docs = b.addInstallDirectory(.{
    .source_dir = autodoc_test.getEmittedDocs(),
    .install_dir = .prefix,
    .install_subdir = "doc/std",
});
if (std_docs) {
    b.getInstallStep().dependOn(&install_std_docs.step);
}

const std_docs_step = b.step(
  "std-docs",
  "Build and install the standard library documentation"
);
std_docs_step.dependOn(&install_std_docs.step);

И этот код появился как раз незадолго до релиза версии 0.11.0. Ранее генерация документации для стандартной библиотеки проходила по другому. Я сам не знаю как. В файле build.zig репозитория языка Zig на GitHub вообще не было строчек про генерацию документации через второй генератор.

[Ниже я бомблю mode off]

Хорошо, что генератор документации стал стабильным. Можно продумывать наполнение документации в свои проекты.

2. Кое-что о языке

2.1 Добавили ошибку компиляции для переменных, которые в коде не меняются

То есть компилятор теперь требует чтобы переменные, который были объявлены как var, но не менялись (им не присваивалось другое значение), принудительно указывать как константы, то есть как const. Считаю, что это правильно. Хотя сам так не делаю =). Если раскрыть мысль, то по моим наблюдениям компилятор раньше и так некоторые переменные переиначивал как константы, несмотря на то, что изначально они указывались как переменные. Теперь же похоже решили принудительно заставлять самих программистов правильно ставить модификатор мьютабельности. И это ради того, чтобы сам программист понимал, что в коде у него не то поведение, которое он сам себе представляет.

Важно! Это изменение потенциально может затребовать поправить ваш существующий код. Рекомендуется иметь в виду.

2.2 Деструктуризация в несколько переменных

Сразу пример (взят из примечаний к выпуску):

const x, var y, z = [3]u32{ 1, 2, 3 };
const x, const y = @Vector(2, u32){ 1, 2 };
const x, const y = .{ 42, runtime };

Надеюсь наглядно понятно, что здесь происходит. Данные массива и структуры могут быть разделены и присвоены по порядку разным переменным в одной строке за раз.

Это новомодная фишка некоторых языков. Даже в C++ такое же добавили. У меня отношения к этому двоякие. С одной стороны это для упрощения. Я это принимаю. Чтобы не копировать одно и тоже слово на несколько строк. Но в тоже время, код может превратиться в цирк с конями, если этим злоупотреблять.

Важно! Так же стоит указать, что срезы нельзя так раскладывать по переменным. Потому что этот механизм требует заранее известный объем данных доступный для распределения. То есть в случае среза нужно сначала конвертировать срез в массив. Для конвертации среза в массив нужно заранее знать границы данных в срезе, которые должны быть переданы в массив.

2.3 Изменили механизм определения равнозначности для специальных типов

Выглядит непонятно. Но суть тут вот в чём, В Zig struct, enum, union, и opaque существуют, как специальные типы. Их можно по сути создавать с нуля. Можно в функции вернуть структуру, как тип, и это станет новым типом. И за счёт этого механизма по сути и создаются новые типы. Невероятно мощная штука. Практически киллер-фитча. И была проблема определения равенства таких новосозданных типов. То есть было сложно понять одинаковые типы или нет, чтобы не создавать дублей. Это влияло на скорость компиляции. Также ниже по статье будет указано про исправление ошибки StreamTooLong. Это исправление, как мне кажется, связано с изменением.

Теперь проверка равнозначности типов смотрит не только на то, что было передано в результат при создании нового типа, но и где именно в коде был сделан новый тип.

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

2.4 Изменили механику обращения к comptime-переменным

Теперь взять указатель на comptime var нельзя. А раньше это можно было сделать. И логика в этом есть, если учитывать, что comptime var это по сути и не переменная вовсе (точнее это переменная, но специфичная). Те кто программируют на C или C++ знают что такое макрос #define. И знают как работает препроцессор. Для программистов на C++ есть более конкретный аналог - constexpr. Но constexpr многогранная личность, потому полным аналогом мне сложно назвать.

Суть в том, что переменная времени компиляции (comptime var), по сути должна быть переменой именно только на время компиляции, для выполнения отведенной ей работы. Далее, после окончания компиляции, значение в переменной - уже конечное готовое значение, которое должно быть подставлено в то место, где оно нужно. Менять это значение во время выполнения программы смысла мало. Поэтому чтобы избавиться от рандомных ошибок сегментации памяти, авторы решили, что comptime var теперь должна быть передана обычной переменной (переменой времени исполнения, если по другому), чтобы значение переменной времени компиляции можно было использовать во время исполнения программы.

Надеюсь я смог объяснить простыми словами. Это изменение на самом деле имеет более глубинные проблемы. Если их раскрывать, то нужно лезть в механику компилятора. А тут мои полномочия всё.

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

3. Кое-что о стандартной библиотеке

3.1 std.os переименовали в std.posix

Заголовок отражает суть. Добавить можно только то, что сам файл os.zig из стандартной библиотеки не удалён. Есть ещё код, который используется. Просто имейте в виду, что std.os не потерялось.

3.2 Изменили представление чисел с плавающей точкой в виде строки по алгоритму ryu

Было

## Errol
3.123456789101112e+00 :f128
3.123456789101112e+00 :f80
3.123456789101112e+00 :c_longdouble
3.123456789101112e+00 :f64
3.12345671e+00 :f32
3.123046875e+00 :f16

Стало

# Ryu
3.1234567891011121314151617181920212E0 :f128
3.1234567891011121314E0 :f80
3.1234567891011121314E0 :c_longdouble
3.123456789101112E0 :f64
3.1234567E0 :f32
3.123E0 :f16

Авторы указывают двухкратное увеличение производительности.

3.3 Переработали код для работы с HTTP

Тут я честно напишу, что очень мало работал с сетью (писал только микросервисы на Go), а в Zig вообще не пробовал. Поэтому ограничусь тем, что убрали некоторые функции, которые раньше только запутывали. По сути сейчас, судя по описанию, стали использовать более консистентные названия. По крайней мере код из примера в примечаниях к выпуску мне понятен (по опыту на Go). А старые варианты я не очень понимал. Пример из примечаний к выпуску:

var read_buffer: [8000]u8 = undefined;
accept: while (true) {
    const connection = try http_server.accept();
    defer connection.stream.close();

    var server = std.http.Server.init(connection, &read_buffer);
    while (server.state == .ready) {
        var request = server.receiveHead() catch |err| {
            std.debug.print("error: {s}\n", .{@errorName(err)});
            continue :accept;
        };
        try static_http_file_server.serve(&request);
    }
}

Полный текст всех изменений здесь.

4. На пути к удалению LLD линкера

Как указано в примечаниях к выпуску, в следующей версии будет удалена зависимость от LLD. А это значит, что пора привыкать к использованию встроенного линкера. На самом деле встроенный линкер уже давно присутствует, и даже не один (как я понимаю - разные для разных платформ, но объединены в одном). По-умолчанию пока ещё используется LLD.

Чтобы это изменить, добавить:

  • для тех, кто компилирует напрямую через компилятор - флаг -fno-lld;

  • для тех, кто использует встроенную систему сборки и скрипт build.zig -значение true переменной use_lld в структурах опций ExecutableOptions, ObjectOptions, SharedLibraryOptions, StaticLibraryOptions или TestOptions, то есть при каждом создании шага компиляции.

5. Правки в системе сборки

5.1 Системные библиотеки теперь проще привязывать

Это нововведение добавляет интеграцию с системными библиотеками. Вещь, которая внутри системы сборки управляет зависимостями, если такая зависимость указана. Это важно для тех, кто будет развертывать проекты на других системах. Где может не быть нужных библиотек.

Делается это через файл скрипта build.zig в него в метод build нужно добавить блок кода, который будет определять необходимость использования системных библиотек. Пример (взят из примечаний к выпуску):

if (b.systemIntegrationOption("groove", .{})) {
  server.linkSystemLibrary("groove");
} else {
  const libgroove_optimize_mode = b.option(
    std.builtin.OptimizeMode,
    "libgroove-optimize",
    "override optimization mode of libgroove and its dependencies",
  );
  const groove_dep = b.dependency("groove", .{
    .optimize = libgroove_optimize_mode orelse .ReleaseFast,
    .target = target,
  });
  server.linkLibrary(groove_dep.artifact("groove"));
}

Метод systemIntegrationOption может принимать опции через второй параметр в виде структуры. В структуре всего одно булево поле default. Если его установить на true, то значит нам по умолчанию нужна системная библиотека. И если она есть, то будет использована, если её нет, то, как я понимаю (я ещё это не проверил) будет использована встроенная.

После чего можно включать или отключать зависимости через аргументы при вызове команды сборки zig build. Пример:

zig build -fsys=z

У меня есть проект, на котором я проверю это нововведение, там как раз такая дилемма с библиотеками. Но, как я уже писал выше, я последнее время больше читатель. И потому всё как-то у меня медленно. Пните в комментариях, если я всё же был не прав в этом пункте.

5.2 Новый аргумент командной строки для системы сборки

Добавили аргумент --release. Он работает в связке со структурой опций оптимизации, где указывается предпочтительный уровень оптимизации.

b.standardOptimizeOption(.{
  .preferred_optimize_mode = .ReleaseFast,
}),

По умолчанию, если не указан предпочтительный уровень оптимизации, при компиляции используется уровень для дебага. И чтобы сменить его на релизный, раньше нужно было указывать аргумент -Doptimize со значениями ReleaseSafe, ReleaseFast, ReleaseSmall. Пример: -Doptimize=ReleaseSafe.

С добавлением нового аргумента стало чуть очевиднее. Чтобы собрать релизный билд достаточно указать аргумент --release. Но здесь есть нюанс. Если в скрипте системы сборки в поле preferred_optimize_mode не указан предпочтительный уровень оптимизации, и при указании аргумента --release не передать значение аргумента, то выйдет ошибка, что предпочтительный уровень оптимизации не указан. Потому что есть три уровня оптимизации для релизных сборк. И требуется указать какой конкретно нужно использовать при компиляции. Для аргумента --release можно передавать значения safe, fast и small. Пример: --release=safe

Тут стоит сделать ремарку, это я типа брюзжу, так как Zig позиционируется как более безопасная замена языка C. И разработчики официально в документации рекомендуют использовать уровень оптимизации ReleaseSafe. Потому у меня возник риторический вопрос, почему при указании аргумента --release без зачения по умолчанию не ставится ReleaseSafe.

5.3 Заменили LazyPath на b.path

Суть в заголовке. Изменили вид представления путей в скрипте системы сборки. LazyPath нас похоже покинет в будущих релизах.

Примеры изменений:

.root_source_file = .{ .path = "src/main.zig" },
  ?
.root_source_file = b.path("src/main.zig"),
.root_source_file = LazyPath.relative("src/main.zig"),
  ?
.root_source_file = b.path("src/main.zig"),

Важно! Это изменение потенциально может затребовать поправить ваш существующий код. Рекомендуется иметь в виду.

6. Систему кэшерование избавили от бага StreamTooLong

Была назойливая ошибка, которая появлялась, если использовались файлы с одни и тем же названием. Я с этой ошибкой пару раз сталкивался. И самое странное в ней было то, что она появлялась не всегда. То есть не случайно в одном проекте. На одном проекте всё могло быть без проблем. На другом же ошибка легко могла появиться, если два разных файла имели одно и тоже название, или разные блоки кода использовали один и тот же набор исходных файлов из другого блока. Проблема решена, и вроде как уже больше никогда не появится. Я лично очень рад этому.

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

Что дальше?

В примечаниях к выпуску указано, что версия 0.13.0 будет нацелена на ускорение компиляции. Это означает, что начнётся работа над изменением компилятора, чтобы полностью уйти от использования LLVM.

Async/Await до сих пор нет. Первоначальная реализация асинхронности выполнение программ через механизм Async/Await была кривой. Её убрали из версии 0.11.0. Используя LLVM реализация была сильно неоптимизированной. И при попытке её оптимизировать оказалось, что это сделать чрезвычайно трудно. А так как будет полный отказ от LLVM, то новую реализацию Async/Await будут делать параллельно уже на новом компиляторе. И возможно появится уже в версии 0.13.0.

upd: возможно я здесь слишком утрирую, и полного отказа не произойдёт, или если и произойдёт, то очень не скоро, не в версии 0.13.0. Не принимайте близко к сердцу. Это я так вижу, со свой колокольни, по той информации, что прочитал о Zig.

На этом всё. Спасибо за внимание!


Ссылки-ссылочки

Основной сайт языка Zig / Он же на русском
Документация языка версии 0.12.0
Документация стандартной библиотеки версии 0.12.0
(Рекомендую читать код самой библиотеки, она читается очень просто. В комментариях кода написано всё тоже самое, что и в веб версии, так как Zig имеет встроенную генерацию документацию из комментариев. И по коду всё же проще ориентироваться)

Важные вехи языка со статусами на Github

Официальный список сообществ по языку в wiki на github

Телеграм чат @ziglang_en
Телеграм чат @ziglang_ru
Телеграм чат @zig_ru
(Говорят там владелец чата странно себя ведёт и поэтому этот чат удалили из официального списка сообществ)

Форум Ziggit
Новостная лента Zig NEWS
Сабреддит r/zig

Сайт для обучения zig.guide только на английском (у них есть гид по 0.13.0-dev)
Сайт для обучения zighelp английский и русский (на очень ранней стадии)
Ziglings: обучение через решение проблем
Набор задачек на Exercism

Страничка Zig на rosettacode c примерами кода (upd от учасника @forthuse)
Репозиторий GitHub zig-tut c примерами обучающих проектов (для Zig версии 0.11.0)
Zig By Example - примеры кода на Zig
(Примеры простенькие, и рекомендуется для начала поизучать сам язык, так как комментариев к коду в примерах нет)

Tags:
Hubs:
Total votes 20: ↑20 and ↓0+25
Comments8

Articles