Pull to refresh

Comments 43

Хм.. Как будто для такой задачи go может еще лучше подойти, что думаете?

Haskell же!

Тоже варианты (D, Go, Rust)

Еще есть c# aot, который специально для этого. И вполне эффективный Nim, если нра Питон - синтаксис.

  1. Удивлён, что версия на Zig получилась короче - он более многословный.

  2. Для парсинга аргументов на С стоило тоже взять готовую библиотеку. Сравнение было бы корректнее

  3. На С тоже элементарно делается defer макросами

Но все же Zig это язык уже нового поколения.

И вообще - это перевод? Потому что актуальный Zig 0.16

Почему для С ТОЖЕ стоило взять готовую библиотеку? Вроде в zig все решено с помощью std. И где вы видели актуальный Zig 0.16? На официальном сайте указана версия 0.15.2

Чтобы сравнивать одинаковые подходы в длину кода.

В даунлоадз и у себя на компе 0.16

Я вот на 0.14 сижу и доволен ;) Ну либо автор написал код какое-то время назад, а статью только сейчас.

К сожалению до релиза 1.0 далеко, когда выйдет уже и не нужен будет наверное.

Так ему уже 10 лет, они его полируют, ломают (-ли) совместимость, чтобы к релизу выдать что-то готовое... Не уверен насчёт достоверности, но, вроде, в этом году выйдет стабильный релиз v1.0

LLM выдала со ссылкой на Telegram-канал: "По некоторым прогнозам, версия 1.0 языка программирования Zig выйдет не раньше 2029 года".

Я читал, что автор - перфекционист и стремится создать надёжный инструмент. Однако процесс версионирования может затянуться на следующие 10 лет. За это время многое может измениться в разработке ПО, учитывая быстрый прогресс LLM.

Если у вас есть точная информация о дате выхода версии 1 из roadmap, буду признателен.

Язык С не прибит гвоздями к Makefile, там уже много разных альтернатив придумали.

И мне интересно, почему zig , а не c++?

Чтобы не страдать с Makefile, cmake, automake, premake и прочими сборочными извращениями, которые современные языки решают обычной сабкомандой init. Уж лучше какой-нибудь cppfront взять.

Ох уж эти страдатели, которые маке не осилили)

Даже и пытаться осиливать не стали, разу взяли Shake ;)

Rust читал месяц. Крутой, но borrow checker для утилиты которая читает файл и пишет в stdout - избыточен.

Так ведь если утилита в одном исходном файле, то borrow checker вы даже не заметите.

В C мне надо было писать fclose(file) перед каждым return, а их у меня было четыре разных в этой функции.

Множественный return вообще не очень хорошая практика. Корректнее использовать единую точку выхода.

К сожалению, в языках где нет возможности определять блок

on-exit;
  // выполняем все необходимые перед выходом действия
  ...
  return;

куда передается управление поде каждого return в функции, или хотя бы поддержки сабрутин (аналог подпрограммы в бейсике - отдельный блок кода внутри функции, вызываемый без создания нового уровня стека и не имеющий собственных аргументов и локальных переменных), позволяющий организовать точку выхода

begsr srExit;
  // выполняем все необходимые перед выходом действия
  ...
  return;
endsr;

и позволяющий вместо

if (...) return;

использовать

if (...) exsr srExit;

использование единой точки выхода может потребовать определенной организации кода для избежания многоуровневых if !error.

 Функции возвращают -1 или NULL при ошибке, смысл ошибки - в errno или нигде.

Это, мягко говоря, не так. Есть понятие "структурированной ошибки". В простейшем виде выглядит вот так:

 typedef struct Qus_EC
    {
       int  Bytes_Provided;
       int  Bytes_Available;
       char Exception_Id[7];
       char Reserved;
       char Exception_Data[];           /* Varying length        */
    } Qus_EC_t; 

Передается аргументом в функцию. На выходе достатчоно проверить

if (Qus_EC.Bytes_Available) {
  // функция заполнила ошибку
  ...
};

Bytes_Provided тут - размер структуры (определяется размером блока дополнительных данных Exception_Data) ; Bytes_Available = 0 - ошибки не было, Bytes_Available > 0 - ошибка была; Exception_Id - код ошибки; Exception_Data - блок дополнительных данных произвольного размера, связанных с конкретной ошибкой.

Остальное тоже весьма спорно. Например

В C у меня было иначе: одна функция process_file делала всё сразу. Добавить новый формат лога = переписывать её всю

А зачем было делать именно так? Почему не разбить эту функцию на несколько более мелких? Например, "диспетчер", определяющий формат лога + набор парсеров под разные форматы? Новый формат - пишем парсер и добавляем его в таблицу парсеров диспетчера.

Такое ощущение, что кинулись писать на С особо не продумав что и как, поплясали с бубном на граблях и потом уже, получив понимание что и как, переписали все на Zig. И 90% "бонусов" не от того, что Zig лучше, а от пришедшего понимания тонкостей задачи.

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

Ну я не спорю. Писать на С требует тренировки мозга - многое приходится в голове держать. Это только годами практики нарабатывается до автоматизма.

В этом плане Rust интереснее, хотя и требует привыкания к заложенным в нем концепциям.

А структурированная ошибка - это целая концепция на уровне системы.

Есть т.н. message file - таблица где содержится код ошибки, ее текстовка (с возможностью подстановки параметров), уровень серьезности (например, 0 - информация, 10 - предупреждение, 20 - критическая). Есть системное API куда передается вот эта структура, содержащая код и значения параметров для подстановки. API по коду находит в message file данную ошибку, берет ее текстовку, подставляет параметры и возвращает полный текст и уровень серьезности.

Разрабатывая сое ПО вы всегда можете расширять message file добавляя туда характерные для вашего ПО ошибки со своими кодами. Функция в общем случае возвращает получилось/не получилось. Если не получилось, вы можете или анализировать ошибку по коду, или же дернуть API и получить полный текст (уже с подставленными параметрами) и дальше выводить его на экран, в лог или еще куда вам нужно.

Плюс эта же ошибка может пробрасываться в виде исключения (языкового или системного, если таковые поддерживаются).

Как-то так по итогу оно и вываливается во втрое больший код, потому что сначала нужно написать систему, чтобы было хорошо - то арену добавь, то работу с массивами скопипасти, то обработку ошибок допиши...

Если речь идет о долгосрочном проекте который потом придется поддерживать и развивать, то оно того стоит.

Ну или если себе строишь набор рабочих инструментов на долгое время.

И в любом серьезном проекте работа с ошибками занимает достаточно большую часть. Если не делать как современные вебклепатели - на все одна ошибка "что-то пошло не так, попробуйте как-нибудь потом"

Если речь идет о долгосрочном проекте который потом придется поддерживать и развивать, то оно того стоит.

Из чего следует необходимость учить ещё один какой-нибудь язык для того чтобы собирать код на си. Можно конечно использовать какой-нибудь nob, чтобы иметь только компилятор в зависимостях, но там свои квирки и segafult приветы будут.

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

Я вот пишу как минимум на двух (то, что в прод идет). Ну и Rust немного для себя... Правда, языки у меня сильно разные. Один хорош для работы с БД и всяких коммерческих расчетов (это основной), но если что-то более низкоуровневое, то там С/С++ лучше.

Часто бывает что один кусок задачи лучше и быстрее решается на одном языке, другой - на другом.

Одна точка выхода:

if (error1) goto one_exit;
...
if (error2) goto one_exit;
...
if (error3) goto one_exit;
...
one_exit:
...release actions...

Если кому то режет глаз goto, то:

while (TRUE) {
  if (error1) break;
  ...
  if (error2) break;
  ...
  break;
  }
...release actions...

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

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

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

Когда условий много делал примерно так:

for (stage=0; stage < MAX_STAGE; stage++) {
  switch (stage) {
    case 1:
      ... блок операций где может возникнуть прерывающая ошибка ...
      break;

    case 2:
      ... блок операций где может возникнуть прерывающая ошибка ...
      break;

    ...
  }

  if (error) break;
}

Чуть хуже с точки зрения производительности, но лучше с точки зрения читаемости. Плюс то, что находится в блоках case вынести в отдельные функции

Но суть, да, в том чтобы обеспечить единую точку входа и единую точку выхода.

do {
    ... блок операций где может возникнуть прерывающая ошибка ...
    break;

    ... блок операций где может возникнуть прерывающая ошибка ...
    break;
} while (0);

А почему не так?

Потому что вам придется после каждого блока проверять if (error) break;

А здесь на каждом обороте цикла отработает один блок (тот, что идет по соотв. номеру) и после этого проверится ошибка.

Наброшу немного за Rust

1)

Ошибки через типы - !T

В Rust еще круче есть - Result<T>: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#recoverable-errors-with-result

2)

Кросс-компиляция из коробки - zig build -Dtarget=x86_64-windows без тулчейнов

В Rust: https://crates.io/crates/cargo-zigbuild

3)

Маленький проект собирается за секунду

В Rust - за доли секунд

4)

Rust - это безопасность ценой сложности

Это ложно. Корректный код на Rust в миллиарды раз проще писать, чем на C, Zig и, даже, Go. Проверено

5)

1089 строк на Zig

На Rust вышло бы и того меньше за счет отсутствия defer (это "фичей" назвать трудно, если честно; только в ироничном ключе), функционального подхода и удобнейших инструментов для работы со строками

6)

Ошибки компилятора длинные

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

7)

Стоит ли переходить на Zig в 2026?

Почти в любом случае - нет, железобетонно. Нужен язык, использовать который можно без боли, быстрый, без gc - Rust, без серьезных альтернатив

Рекомендую, если целью является обучиться крутому инструменту production-ready и стать востребованным разработчиком, изучить Rust и/или Go. C - deprecated, Zig - тупик.

ИМХО

PS: вспомнилось мне интервью на youtube-канале ThePrimeTime с Алексеем Кладовым о Zig и его фичах. Зашла речь про то, что Алексею понадобилось быстренько написать небольшой инструмент для работы с большим строковым файлом. Он ответил, что выбрал Rust. Очень показательно )
https://www.youtube.com/watch?v=V8Bg55lTUCw&t=3701s

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

Но на это требуется время, сразу с наскока может не получиться - пока не проникнешься, все будет казаться мучительно и больно.

На Rust вышло бы и того меньше за счет отсутствия defer

Скорее всего не стало бы меньше а +/- столько же, сколько и у Zig, если писать единым rs. Обработка result наверняка заполнила эти пробелы.

Парсил несколько гигов хмлников на шарпе за 5 секунд, от проца и ио конечно зависит, но чистый си это всегда геморрой конечно. Не знаю кто такой зиг, но если бы дожал на си, было бы быстрее.

Zig тоже LLVM. Так что по компиляции будет паритет

Читаю статью - и мозг постоянно цепляется за нейросетевые паттерны текста. Автор, признайся честно - нейросетка писала статью?

Очевидно, что это вывод LLM. А так как автор студент, то меня преследует смутное сомнение, что это один из тех случаев, когда преподаватель ставит задачу "написать на хабр". Сколько раз так уже было.

Страшная, конечно, смесь - студент и LLM.

пишешь CLI-утилиты, системные инструменты, что-то встраиваемое

Ну не знаю, не знаю, как-то сомнительно если честно. Для перового и второго лучше взять что-то из троицы C++, Rust, Go. Для встраиваемых систем можно использовать Rust если уже пишешь на нём, если же до этого писал на C/C++, смысла переходить особо нет, максимум какую головную боль вылечишь - безопасный код писать можно, но ты не обязан.

И моя личная головная боль - сертификация по EN 50128, где по факту C это стандарт, он просто самый предсказуемый и проверяемый, и сертификация под него дешевле, а Ferrocene пока не дотягивает. Тем более что зачастую есть только компилятор для C99, о существовании C++ разработчики не слышали, и приходится терпеть терпеть, как например для 8-16-разрядных контроллеров. А с учётом жизненного цикла кода в 25 лет, Rust или что-то другое я увижу не скоро.

сертификация по EN 50128

C23 самую большую дырку с массивами закрыл. И с многопоточкой поработали

Он раньше пройдёт все сертификации.

А Раст туда попадет не раньше чем no-panic и nostd стандартизуют

Zig выигрывает у многих для эмбеда за счёт идеологии - никаких скрытых действий (аллокаций, паник, исключений итп) и кросс компиляции из коробки.

А если переписать эту утилиту с zig на Python, получится еще в два раза меньше строк кода.

И время работ с гигабайтными файлами сократится с пары секунд до жалких минут, да.

И производительность упадет раз так в 1000.

Интересный вопрос. Думаю, не в 1000 раз, а меньше.

Куда уж меньше ))
Если серьезно, Python по бенчмаркам в 10 раз медленнее PHP, а это оба интерпретируемые языки, куда им до скомпилированного машинного кода.

Плюсовый иострим может с этим поспорить =)

Сразу скажу что сегфолты - это когда прога пытается залезть в память к которой ей нельзя.

Тут надо уточнить, что если сегфолта нет (то есть прога залезла в память, к которой ей можно), это все равно не исключает ошибки. Потому как не факт, что это именно та часть памяти, которая подразумевалась.

Sign up to leave a comment.

Articles