Не так давно на волне интереса к низкоуровневым языкам программирования, я не удержался и решил дать шанс Zig. Последнее время будто бы настал небольшой ренессанс интереса к системным языкам программирования, который будто бы начался с Rust в 2015 году. Rust будто бы давал обещание окончательно разрешить проблему безопасности при работе с памятью, что является ключевой проблемой для C/C++, которые были стандартом в этой области в течении многих десятков лет. Но будто бы с тех пор стали появляться новые языки, которые пытаются решить проблему безопасности, при этом лишь пытаясь “улучшить” Си, не превращаясь при этом в монструозный набор сложности как C++. Потребность в таких языках есть и в геймдеве (Jai, Odin) и в по настоящему низкоуровневых вещах вроде ОС и драйверов (Nim, Hare, C3) - хотя кажется, что за каждым из этих языков стоит определённый одиозный программист :).
Но самым популярным после Rust из этих языков стал пожалуй Zig. Ниже несколько моментов, которые меня впечатлили (и возможно я остановлюсь на Zig на какое-то время).
Простота и понятность
Нужно ли говорить, что C++ и Rust это довольно сложные языки? На изучение и принятие borrow checker в Rust у многих уходят месяцы, а С++ по словам одного моего знакомого это на каждом проекте новый язык программирования - настолько велико наследие десятков лет развития. Другая крайность - это конечно Си.
Zig просто по количеству синтаксиса конечно намного опережает Си, однако же эта “простота” в Си относительно обманчива. В Си нам не обойтись без CPP (Preprocessor C), который по сути является надстройкой над языком
Никаких сюрпризов
Это пожалуй моя любимая особенность в Zig. Страница с документацией языка прямо заявляет нам, что одна из целей языка - это быть предельно ясным. На практике это означает:
- Никаких Си подобных макросов - не нужно гадать является ли код макросом, или целью макроса.
- Нет скрытого “control flow”, никаких перегрузок операторов, или @property, который может скрывать неявный вызов функции.
- Нет “скрытых” выделений памяти (в отличие от Rust, например, который требует от программиста хорошего понимания системы выделения памяти).
Но момент, который зацепил меня, это то, что в Zig нет интерфейсов - кажется странным выделять это в явный плюс, но их
нет как синтаксической конструкции. А так интерфейс реализуется через соответствующую структуру, которая содержит ссылки на создающий его
объект - именно примерно так это и работает в какой-нибудь Java, но при этом всё также явно должно быть реализовано программистом.
Пример writer
интерфейса отсюда:
// anyopaque - ссылка на любой тип данных, что-то вроде void* в Си
// такая же ссылка и на функцию в типе-создателе writer'a
const Writer = struct {
ptr: *anyopaque,
writeAllFn: *const fn (ptr: *anyopaque, data: []const u8) anyerror!void,
fn writeAll(self: Writer, data: []const u8) !void {
return self.writeAllFn(self.ptr, data);
}
};
Читая любой Zig код можно быть уверенным - он делает именно то, что там написано - большая разгрузка мыслительного процесса на самом деле.
Работа с ошибками
Про классический паттерн try-catch
уже было много сказано, особенно в свете того, что для системного программирования он довольно проблематичен (см. например, пост про Why should I have written ZeroMQ in C, not C++ создателя ZeroMQ).
В то же время в классическом Си работа с ошибками обычно (не будем рассматривать errno) заключатеся в возвращении int значения, которому соответствуем enum. Естественно, это не всегда удобно, когда нужно
вернуть валидное значение int, а не ошибку, и кроме того, все эти
проверки на ошибки на совести программиста.
Модель в Zig использует этот же подход, но при этом позволяет вернуть
так называемый tagged union, который может содержать в себе как ошибку, так и валидное значение.
Ограниченная строгость
В Zig нет модели, которая обеспечивала бы memory safety, аналогичной Rust. Ближашая аналогия здесь - это как если бы компилятор Си строго относился к каждому случаю когда функция может вернуть ошибку, или
указатель может быть NULL. Каждый такой вариант обязан быть обработан, но способы для этого довольно эргономичны - например слово try
заставит в случае возврашения ошибки немедленно вернуть эту ошибку из текущей функции. Кроме того, компилятор Zig велживо предотвращает классические Сишные undefined behavior, вроде выхода за границы массива, или использования неинициализированных переменных.
Естественно, это не означает, что Zig - это безопасный язык, но он позволяет избежать многих ошибок, которые очень естественно возникают в Си.
Перспективы
Язык, естестенно, ещё очень молодой, и стандартная библиотека активно развивается (хотя core языка кажется уже весьма стабильным). Инструменты вроде LSP и пакетного менеджера также по отзывам ещё довольно сырые (хотя у меня не возникло серьёзных проблем с ними). Полное отсутствие вакансий и неясные перспективы по “принятию” языка в IT мейнстрим делают его конечно не очень привлекательным для поиска работы. Но как вариант прикоснуться к низкоуровневому программированию, при этом с минимальными страданиями, с которыми у меня ассоциируются C/C++, Zig не самый плохой вариант. Уже на текущий день есть 3 больших проекта на Zig, которые сигнализируют, что язык развивается и развивается активно.
- Bun - быстрый JavaScript рантайм
- TigerBeetle транзакционная база данных с прицелом на финтех, которая обещает скорость сравнимую с OLAP системами.
- Ghostty - эмулятор терминала.
Немало также и проектов уровнем поменьше, которые также развиваются очень активно, поэтому я рассчитываю, что у языка будет хорошее будущее.