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

По случаю выхода версии 0.11.0 языка Zig я решил написать статью о том, что привлекло меня в языке, что мне в нём нравится. Сам язык Zig имеет ряд интересных решений, которые выделяют его на фоне других «убийц» языка C. Коротко:

  • встроенная система сборки;

  • прямое использование заголовочных файлов написанных на C;

  • компиляция кода написанного на C компилятором Zig;

  • выполнение comptime кода во время компиляции;

  • механизм аллокации памяти с возможностью выбора аллокатора;

  • и другие.

Причина, по которой я решил изучать Zig - я не захотел полноценно учить C. Не то, чтобы я не знаком с C. Я с ним столкнулся очень давно, лет двадцать назад, но тогда я быстро переключился на Pascal(Delphi), а потом уже на C++, так как у них было больше возможностей самих языков в сравнении с C. Сейчас же, видя во что превращается C++, я решил сделать шаг назад. Pascal и Delphi ушли для меня в сторону. И я решил вернуться к C, так как по синтаксису он мне ближе, и я сам больше в сторону системного программирования склоняюсь. Я был удивлён тому, что язык C в сравнении с C++ изменился мало. Это с одной стороны хорошо - стабильность, портируемость и всё такое. Но с другой стороны - большие проекты написанные на C не только сложно читать, но и сложно поддерживать. Я как-то с GTK решил поработать. И был неприятно удивлён его внутренней структурой, особенно с учётом Glib с его GObject. Помимо GTK были и другие проекты на C, и там тоже не сказать, что приятный код. А ещё с моей привычкой работать в C++ стиле, меня бесило дублирование одних и тех же слов в названии функций и в первом параметре функции, которая работает с данными структуры. В общем, с точки зрения C++ программиста у меня много претензий к C, но это всё лирика.

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

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

Чем же меня так привлёк язык Zig?

Читаемость кода

Ниже пример из моего эксперимента с raylib. Habr не умеет разукрашивать код Zig. Но даже так читаемость хорошая.

const std = @import("std");
const raylib = @import("raylib");
const ResourceManager = @import("resourcemanager.zig").ResourceManager;
const ScreenManager = @import("screenmanager.zig").ScreenManager;

const allocator = std.heap.c_allocator;

pub fn main() !void {
    const screen_width = 800;
    const screen_height = 450;

    raylib.Window.init(screen_width, screen_height, "Test Window");
    defer raylib.Window.close();

    raylib.AudioDevice.init();
    defer raylib.AudioDevice.close();

    var resouce_manager = try ResourceManager.init(allocator);
    defer resouce_manager.deinit();

    const music = try resouce_manager.loadMusic("resources/ambient.ogg");
    music.setVolume(1.0);
    music.play();

    const sfx_coin = try resouce_manager.loadSound("resources/coin.wav");
    sfx_coin.setVolume(1.0);

    const font = try resouce_manager.loadFont("resources/mecha.png");
    _ = font;

    var screen_manager = try ScreenManager.init(allocator);
    defer screen_manager.deinit();

    const target_fps = 60;
    raylib.setTargetFPS(target_fps);

    while (!raylib.Window.shouldClose()) {
        music.update();

        raylib.beginDrawing();
        defer raylib.endDrawing();

        screen_manager.update();
        screen_manager.draw();
    }
}

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

Favor reading code over writing code.

Если интерпретировать на русский, то выйдет примерно так:

Предпочтение удобства чтения кода перед удобством написания кода.

И пока что разработчики соблюдают этот подход. Есть только один момент, который для меня совсем выбивается из общего вида. Это конструкция вида (взято из документации):

var y: i32 = 123;

const x = blk: {
    y += 1;
    break :blk y;
};

Здесь именованный блок blk представляет собой выражение, которое возвращает значение через break. То есть вместо кода y += 1 может быть полноценный алгоритм вычисления чего-то для переменной x. Это что-то типа лямбды, но это не лямбда. Конструкция break :blk y; - это остановка выполнения блока blk и возврат значения y. Почему имеет такой вид? Для меня загадка. Я понимаю как это работает, понимаю, что можно внутри этого блока встроить ещё именованные блоки, и потом возвращаться к началу из вложенных блоков, но глаз каждый раз цепляется за эту конструкцию.

Всё есть «Структура»

На самом деле это не совсем так, но для упрощения понимания то, что меня привлекло я обозначу это именно так. Это примерно, как в языке Lua, где «Всё есть «Таблица» (table). Из кода моего эксперимента с raylib видно, что загрузив нужный модуль через встроенную функцию @import, программист через псевдоним, как бы обращаясь к элементам структуры, получает доступ ко всем внутренним полям, структурам, объединениям, энумерациям, функциям и другим элементам этого модуля, которые обозначены как pub, то есть те, что являются публичными. Для понимания сути «псевдонима» можно провести аналогию с typedef из C или using из C++. В примере выше функция main то же публичная. Сразу видно отличие от языка C, в Zig элементы кода по умолчанию «приватные», в то время как в C они «публичные». В C ключевое слово static играет роль «ограничителя», и оно полно своих особенностей. В Zig же более прозрачно представлен доступ. Есть только одно отличие от общего правила в Zig. Поля структур (struct), объединений (union), эмунераций (enum) и других похожих элементов могут быть только публичными. То есть поведение повторяет язык C.

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

Есть в Zig ещё ключевое слово usingnamespace. Если его использовать при импорте, то можно загрузить содержимое файла без псевдонима. Примерно так:

usingnamespace @import("file.zig");

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

Внутренние функции в теле структур, объединений, энумераций

В Zig в тело структуры (struct), объединения (union) или в энумерации (enum) можно вписывать функции. И по сути имя конкретного элемента становится пространством имён для функции. Ниже ещё один пример из моего эксперимента с raylib.

const AudioStream = @import("audiostream.zig").AudioStream;

pub const Sound = extern struct {
    stream: AudioStream,
    frameCount: c_uint,

    pub fn load(filename: [:0]const u8) Sound {
        return LoadSound(@ptrCast(filename));
    }

    pub fn isReady(self: Sound) bool {
        return IsSoundReady(self);
    }

    pub fn update(
        self: Sound,
        data: *const anyopaque,
        sample_count: i32,
    ) void {
        UpdateSound(self, data, @as(c_int, sample_count));
    }

    pub fn unload(self: Sound) void {
        UnloadSound(self);
    }

    pub fn play(self: Sound) void {
        PlaySound(self);
    }

    pub fn stop(self: Sound) void {
        StopSound(self);
    }

    pub fn pause(self: Sound) void {
        PauseSound(self);
    }

    pub fn doResume(self: Sound) void {
        ResumeSound(self);
    }

    pub fn isPlaying(self: Sound) bool {
        return IsSoundPlaying(self);
    }

    pub fn setVolume(self: Sound, volume: f32) void {
        SetSoundVolume(self, volume);
    }

    pub fn setPitch(self: Sound, pitch: f32) void {
        SetSoundPitch(self, pitch);
    }

    pub fn setPan(self: Sound, pan: f32) void {
        SetSoundPan(self, pan);
    }
};

И доступ к функциям описанных внутри конкретного элемента теперь возможен двумя способами (оба способа равнозначны):

const Sound = @import("sound.zig").Sound;

const some_sound = Sound.load("somesound.wav");
Sound.play(some_sound); // первый способ
some_sound.play(); // второй способ

Первый способ работает для любых функций внутри конкретного элемента. Второй способ работает, если переменная является экземпляром конкретного элемента, и первым параметром функции передаётся экземпляр этого конкретного элемента, внутри которого находится функция. Например, в коде примера структуры Sound тип возвращаемого значения функции load сама структура Sound. А функция play имеет входной параметр с типом этой структуры. То есть в коде примера вызова функций переменная some_sound - это экземпляр структуры Sound. А это значит можно вызывать подходящие функции структуры способом попроще. Это то, что мне очень не хватает в C.

Встроенная система сборки

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

Ниже пример из всё того же моего эксперимента с raylib.

const std = @import("std");
const raylib_zig = @import("raylib-zig/build.zig");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});
    const target = b.standardTargetOptions(.{});

    const raylib_options = b.addOptions();
    raylib_options.addOption(bool, "platform_drm", false);
    raylib_options.addOption(bool, "raygui", false);

    const exe = b.addExecutable(.{
        .name = "raylib-test",
        .optimize = optimize,
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
    });

    const system_raylib_state = b.option(
        bool,
        "system-raylib",
        "link to preinstalled raylib libraries",
    ) orelse false;

    if (system_raylib_state) {
        exe.linkSystemLibrary("raylib");
    } else {
        exe.linkLibrary(raylib_zig.createCompileStep(b, .{
            .target = target,
            .optimize = optimize,
        }));
    }

    const raylib_lib = b.addModule("raylib", raylib_zig.modules.raylib);

    exe.addModule("raylib", raylib_lib);

    // exe.install();
    const install_exe = b.addInstallArtifact(exe, .{});
    b.getInstallStep().dependOn(&install_exe.step);

    // const run_exe = exe.run();
    const run_exe = std.build.RunStep.create(b, "run raylib-test");
    run_exe.addArtifactArg(exe);

    run_exe.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_exe.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_exe.step);

    // -------------------------------------------------------------- Tests --
    const exe_tests = b.addTest(.{
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });

    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&exe_tests.step);
}

Прокоментирую пару мест:

// exe.install();
const install_exe = b.addInstallArtifact(exe, .{});
b.getInstallStep().dependOn(&install_exe.step);

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

// const run_exe = exe.run();
const run_exe = std.build.RunStep.create(b, "run raylib-test");
run_exe.addArtifactArg(exe);

Абсолютно аналогичный пример. В комментарии официальный упрощённый вариант.

И здесь я отмечу еще один плюс, о котором я указывал ранее. Для компиляции кода на языке Zig не нужно указывать все .zig файлы в файле сборки, как в ряде других языков. Достаточно указать только первый (основной) файл, а компиляция рекурсивно соберёт все файлы, которые нужны. То есть, если программист использует некую структуру где-то в коде, но структура находится в другом файле, то импорт файла с этой структурой программист не сможет не добавить в код. А это значит и потерять по пути то же не сможет. У - удобно.

Но это не работает с кодом на языке C. При сборке всё равно необходимо указывать все используемые .c или .cpp (cc, mm и др.) файлы. И если этого не сделать, то компилятор скажет, что «не знает таких символов» в компилируемом коде.

Нехватка нужных .c (.cpp, cc, mm и др.) фалов заметная проблема, особенно в больших проектах. Мельком упомяну обратную проблему для языков типа C и C++, когда код не нужен, а файлы с кодом всё еще находятся в скриптах сборки. Код из этих файлов вызываться конечно не будет, но сами файлы в компиляции учавствовать будут. А это значит, что время компиляции не сократиться, и выходной размер готового файла не уменьшится.

А почему не Rust?

На этот вопрос я специально оставил ответ на конец. И ответ банален и прост. Rust не замена C. Вообще. Это не значит, что его нельзя использовать вместо C. Отнюдь. И даже совместно можно. Но не так, как можно С совмещать с Zig. Одна из киллер фитч языка Zig, возможность напрямую взаимодействовать с кодом языка C. Хотя Zig в этом не уникален. Есть ряд других языков (C2, C3, V, Vox, и ещё несколько языков, названия которых я забыл), которые делаю практически то же самое, что и Zig. Пытаются заменить C, как язык системного программирования и межпрограммного использования библиотек. В языке Zig взаимодействие с языком C работает в обе стороны. То есть можно написать библиотеку на Zig, соблюдая некоторые правила, которую можно будет использовать в проекте написаном на C, или на другом языке, где можно подгружать библиотеки написанные на C.

И Zig мне понравился больше. Мне кажется, что у него есть будущее.

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

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

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

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

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

Форум Ziggit
Новостная лента Zig NEWS

Сайт ziglearn для обучения
Ziglings: обучение через решение проблем
Zig By Example - примеры кода на Zig
(Примеры простенькие, и рекомендуется для начала поизучать сам язык, так как комментариев к коду в примерах нет)

Есть сабреддит r/zig, но он теперь только для чтения после известных событий с закрытием бесплатного API реддита.

Комментарии (92)


  1. AnimeSlave Автор
    07.08.2023 13:57
    +2

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

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


  1. MountainGoat
    07.08.2023 13:57
    +2

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

    В Cargo то же самое, только он не обязателен для простых случаев.


    1. AnimeSlave Автор
      07.08.2023 13:57

      По умолчанию при создании проекта из cargo создается файл Cargo.toml, который отвечает за настройку сборки. В Zig сразу создаётся файл build.zig, и настройка выполняется через него. Может быть в будущем произойдёт переход на упрощение сборки, но пока именно так


      1. unC0Rr
        07.08.2023 13:57
        +1

        Лучшая идея, что встречалась мне. Даже Rust (при наличии Cargo) и Go уступают. Суть в том, что файл, который управляет сборкой проектов для Zig — это файл с кодом на языке Zig
        В Zig сразу создаётся файл build.zig, и настройка выполняется через него. Может быть в будущем произойдёт переход на упрощение сборки
        Т.е. идея лучшая, но получилось сложно. В чём же тогда плюсы этой идеи?


        1. AnimeSlave Автор
          07.08.2023 13:57

          ...но получилось сложно.

          Почему у вас такой вывод? Я писал о том, что сложно?


          1. freecoder_xx
            07.08.2023 13:57

            Может быть в будущем произойдёт переход на упрощение сборки, но пока именно так


      1. IvaYan
        07.08.2023 13:57
        +1

        По умолчанию при создании проекта из cargo создается файл Cargo.toml, который отвечает за настройку сборки.

        Да, потому что чаще всего декларативного описания хватает. Но если не хватает, то можно и к императивному подходу обратиться. А в Zig сразу императивный, причём вы сначала говорите, что это лучший подход, а потом пишете, что, возможно, Zig перейдет на более простой метод. То есть подход лучший, но не простой? Чем же он тогда лучший?


        1. AnimeSlave Автор
          07.08.2023 13:57

          То есть подход лучший, но не простой?

          И у вас такой же вывод. Может быть я не правильно выразился. Или вас слово «лучший» задевает, что Rust в этом контексте не лучший? Тогда попробую уточнить.

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


          1. Mingun
            07.08.2023 13:57
            +1

            Это все хорошо до тех пор, пока не выясняется, что тулинг теперь без компилятора не способен понять, а какие у вас там зависимости. Запускать непонятно кем написанный код, чтобы поизучать в IDE проект — ну такое себе


            1. EvilFox
              07.08.2023 13:57
              +3

              Запускать непонятно кем написанный код, чтобы поизучать в IDE проект — ну такое себе

              То ли дело:

              https://github.com/eleijonmarck/do-not-compile-this-code

              https://github.com/lucky/bad_actor_poc

              https://rust-analyzer.github.io/manual.html#security


              1. freecoder_xx
                07.08.2023 13:57

                Могут быть проблемы не только с макросами, но и билд-"скриптами", если мы говорим о сборке (разворачивание процедурных макросов в IDE ведь можно отключить). Скорее речь о том, что наличие процедурных макросов, билд-файлов и левых зависимостей проще отслеживать в Rust-проектах. Грубо говоря, если вы скачали hello-world и в воркспейсе проекта есть build.rs или определён крейт с процедурным макросом, или имеются какие-то левые зависимости не из crates.io, то это повод провести тщательный аудит проекта до запуска.


                1. MountainGoat
                  07.08.2023 13:57

                  Но очень нужен метод определения, есть ли процедурный макрос в crate по его названию. Я пробовал Таро (меня бабушка учила), но точность не очень.


            1. AnimeSlave Автор
              07.08.2023 13:57

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

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


              1. Mingun
                07.08.2023 13:57
                +1

                Ну, нет. Условный crates.io в одном случае должен распарсить файл известного формата своим доверенным парсером, чтобы понять зависимости вашего проекта, а в другом — выполнить недоверенный код.


                1. AnimeSlave Автор
                  07.08.2023 13:57

                  ...а в другом — выполнить недоверенный код.

                  Очень странно. Это как программист добавляя в код библиотеку может ей не доверять?

                  Я понял вашу изначальную претензию, но тут проблема уже восприятия. Я не вижу проблем использовать скрипт, это примерно тоже самое, что и скрипты для cmake, meson, gradle и другие системы сборки. Как тюллинг работает с ними? Вопрос риторический. Потому и мне кажется, что претензия странная


                  1. Mingun
                    07.08.2023 13:57

                    Очень странно. Это как программист добавляя в код библиотеку может ей не доверять?

                    Как бы владельцы (кода) crates.io и программист, загружающий в реестр свою библиотеку со скриптом сборки — это разные люди...


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


          1. IvaYan
            07.08.2023 13:57
            +1

            И у вас такой же вывод. Может быть я не правильно выразился.

            Просто вы пишете, что это -- лучшая идея. А потом сразу же пишете, что это сложно и настройка билда в одном файле [как в Rust] было бы упрощением. На мой взгляд это немного противоречит друг другу.

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

            Как-то вы неожиданным образом перешли от использования скрипта для сборки к подгрузке внешней библиотеки (это какой? crate это не библиотека, он будет вкомпилен в ваш бинарник как составная часть, после компиляции внешним он не будет) через функциональность компилятора. Или чтобы использовать скрипт для сборки надо какую-то внешнюю библиотеку использовать? Нет, не нужно, компилятор это из коробки умеет.


            1. AnimeSlave Автор
              07.08.2023 13:57

              А потом сразу же пишете, что это сложно

              Я этого не писал. Не придумывайте. Я не считаю срипты сборки сложным элементом. То что я имею в виду про упрощение, это, как я это вижу, улучшение инлюзивности. То есть такое в будущем возможно. Нужно ли?


            1. AnimeSlave Автор
              07.08.2023 13:57

              crate это не библиотека, он будет вкомпилен в ваш бинарник как составная часть, после компиляции внешним он не будет

              %шутка_про_милиниалы_изобрели%

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


              1. AnthonyMikh
                07.08.2023 13:57

                В Rust необходим unsafe контекст для работы с C, что накладывает свои требования к написанию кода.

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

                Удивительно!


  1. VBKesha
    07.08.2023 13:57
    +9

    pub fn main() !void

    Нет ничего, что вызывало бы вопросы.

    Ну ладно pub скорей всего публичная, fn видать функция, main её имя, в скобках могли быть ваши параметры, но !void это что значит возвращает НЕ void? Вот честно мне не очень понятно.


    1. simenoff
      07.08.2023 13:57
      +1

      Объявление!voidуказывает на то, что функцияmainне возвращает никакого значения, но может генерировать ошибки


      1. VBKesha
        07.08.2023 13:57
        +7

        Ну вот честно догадаться об этом очень сложно, и вопросы оно таки вызывает. Особенно учитывая что ниже по коду ! используется просто как отрицание.


        PS. Почему если уже впихнули слова pub и fn нельзя было добавить слово обозначающее что может быть ошибка, почему это ушло в часть возвращаемых значений. Это вопросы не к вам конечно, просто мысли в слух....


      1. IvanPetrof
        07.08.2023 13:57
        +1

        Простите, а если функция должна возвращать целое, нужно писать

        int или !int? Вот в чём вопрос..


        1. AnimeSlave Автор
          07.08.2023 13:57
          +1

          Целочисленный тип в Zig обозначается i32 (знаковый) или u32 (беззнаковый), но при этом цифра после может быть любая (вплоть до 65535). То есть этот тип может содержать необходимое программисту количество бит.


          1. domix32
            07.08.2023 13:57

            Вопрос так и остался без ответа - вертать i32 или !i32?

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

            А jai/odin не пробовали?


            1. AnimeSlave Автор
              07.08.2023 13:57

              Вопрос так и остался без ответа - вертать i32 или !i32?

              Просите. Волнуюсь. Первая статья.

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

              Если не обрабатывать ошибки, и что-то внутри функции «крякрет» (то есть бросит ошибку), приложение упадёт. Это официальное закрепленное поведение.

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

              Пример синтаксиса:

              fn foo() Error!void {
                  const x = try bar();
                  buz(x);
              }
              
              fn foo2() void {
                  const x = bar() catch 13;
                  buz(x);
              }


              1. domix32
                07.08.2023 13:57
                +1

                То есть поведение аналогично uwrap_or_else у Result в Rust. Понял, принял.


            1. AnimeSlave Автор
              07.08.2023 13:57

              А jai/odin не пробовали?

              Я про них забыл. Пробовал. Мне не понравился синтаксис у них, оба очень схожи, хотя позиционируются создателями несколько иначе. Заменой C они явно не станут. Работа с библиотеками на C у них иная. Сложнее чем в Zig


            1. AnimeSlave Автор
              07.08.2023 13:57

              На днях вышла статья (на английском). Как раз про обработку ошибок в Zig


    1. AnimeSlave Автор
      07.08.2023 13:57
      +2

      Вот честно мне не очень понятно.

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

      Про читаемость я имел в виду отсутствие многоуровневых блоков, которые просто визуально сложно воспринимать. Те же макросы в C. Особенно когда их много вложенных друг в друга. Сами по себе макросы не проблема. Проблема то как их используют. Или указатели на функции в C. Что-то типа такого:

      void (*operations[3])(int, int)

      И это не самый проблемный пример


      1. VBKesha
        07.08.2023 13:57
        +1

        Восклицательный знак перед возвратным типом означает, что функция может вернуть ошибку.

        Я выше писал, почему не сделать нормально читаемое слово перед в объявлении функции чтобы было типа:


        pub canerror fn main() void {

        или типа того, зачем мучать бедный ! двумя операциями, а тех кто на этом пишет, заставлять помнить в каком месте заклинания ! какое значение имеет?


        1. AnimeSlave Автор
          07.08.2023 13:57
          +1

          В Zig можно определять конкретный тип ошибки для возврата. Очередной пример из моего эксперимента с raylib:

          pub fn init(a: Allocator) Allocator.Error!ResourceManager {
              return .{
                  .allocator = a,
                  .resources = ResourceList.init(a),
              };
          }

          Тип ошибки располагается перед восклицательным знаком. И длинные возратные типы выглядят как «хлам». А как «хлам» лучше читается? В начале функции, или в конце?

          После того как в C++ появились конструкции типа этой:

          auto main() -> int {
              return 0;
          }

          Читаемость чуть улучшилась, так как выравнялись строки. И поиск глазами стал проще

          В Zig можно почти словами читать: публичная функция init с параметром a типа Allocator возвращает Allocator.Error или ResourceManager


          1. Sklott
            07.08.2023 13:57
            +2

            Тогда было бы логичней использовать |. Он везде читается как "или".


            1. AnimeSlave Автор
              07.08.2023 13:57
              +2

              Это относится к первичному значению восклицательного знака. Типа «Внимание!». Или «Будьте внимательны!». Это странно только пока об этом не знаешь. Первый раз, когда я только узнал о Zig, был примерно аналогичный момент. Но в голове как-то сразу отложилось, что это бред какой-то. Как-то не вяжется системный язык программирования и возможность возвратить любое значение кроме `void`. А когда прочитал о возвратах ошибок понял что к чему


          1. VBKesha
            07.08.2023 13:57

            Читаемость чуть улучшилась, так как выравнялись строки. И поиск глазами стал проще

            Честно я в основном в Си работаю. Но я правильно понимаю что это тот же самый
            int main() {
                return 0;
            }

            Только мы добавили слово auto и -> и теперь начиная читать строку я вижу что main вернет хрен пойми что, потом понимаю что int. С++никам конечно видней, но по мне читаемость вот именно в этом примере стала только хуже.


            1. AnimeSlave Автор
              07.08.2023 13:57
              +1

              ...в этом примере стала только хуже.

              Почему? Представьте, что у вас не одни main, а пачка функций. Сотни. И у всех свои возвратные значения разной длинны написания. Читать не очень удобно. За года, конечно, привыкаешь к этому. Я тоже привык. Но до сих пор бывают моменты, когда меня задевало то, что возвратный тип иногда был слишком длинным и мешал читать код. Есть варианты написания типа возвратного значения и названия функции на разных строчках. Типа:

              int
              main() {
                  return 0;
              }

              Но он не улучшает читаемость. Только после того, как в C++ появился вариант с auto стало чуть лучше.

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


              1. VBKesha
                07.08.2023 13:57
                +1

                Сотни. И у всех свои возвратные значения разной длинны написания. Читать не очень удобно.

                Видимо я слишком молод/стар чтобы понять эти заморочки. Хотя писать доводилось на разных языках


                Кончено, лаконичность C меня тоже манила

                Вот она и сейчас манит. Поэтому все эти fn, вроде оно короче конечно чем function, а вроде и вообще не нужно.
                В общем вкусовщина, но если кому-то нравится то почему бы и нет.


                1. AnimeSlave Автор
                  07.08.2023 13:57

                  Видимо я слишком молод/стар чтобы понять эти заморочки.

                  Когда приходится многократно дописывать, переписывать, свой же код, то начинает лезть желание код привести в читаемый вид. А с точки зрения C-подобных языков это сложно, так как синтаксис исторически так сложился. Поэтому я лично за то, чтобы и в них что-то похожее завезли. Но этому уж точно не бывать в C. В C++ есть еще надежда, если Херб Саттер продавит свой C++2 в комитете по стандартизации


            1. EvilFox
              07.08.2023 13:57

              С++никам конечно видней

              Return trailing type здесь излишне и абуз языка.


          1. SpiderEkb
            07.08.2023 13:57

            Ну вообще, есть много других способов описания функций с достаточно неплохой читаемостью. Например:

                  dcl-proc cvtDte2CYMD export;
                    dcl-pi *n zoned(7: 0);
                      dte  char(10) value;
                    end-pi;

                    dcl-s rslt zoned(7: 0) inz;

            ...

                    return rslt;

                  end-proc;

            dcl-proc - declare procedure
            Дальше - имя процедуры (смещение от начала строки всегда одинаковое)

            После имени - различные модификаторы (в данном случае - процедура экспортируемая)

            Дальше - блок dcl-pi - declare procedure interface - описание интерфейса:

            *n zoned(7: 0) - *n - неименованный параметр, zoned(7: 0) - типа параметра. Это тип возвращаемого значения.

            Внутри блока перечисление аргументов процедуры. В данном случае он один.

            dte  char(10) value; - аргумент - имя, тип, модификатор (в данном случае - передается по значению).

            Далее объявления локальных переменных (dcl-s - declare standalone variable, может быть еще dcl-c - declare constant или dcl-ds - declare data structure или dcl-f - declare file).

            Далее понятно - тело процедуры, возврат значения. И конец - end-proc.

            Все достаточно читаемо.

            Прототип в заголовках определяется похоже:

            dcl-pr cvtDte2CYMD zoned(7: 0) extproc('cvtDte2CYMD');
              dte  char(10) value;
            end-pr;

            Тут все проще - dcl-pr - declare prototype, далее имя процедуры, тип возвращаемого значения, модификаторы (в данном случае ссылка на экспортируемое имя).

            Внутри блока - список аргументов как в dcl-pi в самой процедуре.

            и опять - отступ всех имен фиксирован от начала строки. Все подробности потом.


            1. AnimeSlave Автор
              07.08.2023 13:57

              Такой Pascal стайл. Тоже хорошо читается


              1. SpiderEkb
                07.08.2023 13:57

                На самом деле это RPG (IBM'вский язык для коммерческих расчетов). Но в этих вот dcl-... прослеживаются корни из PL/I


    1. andreishe
      07.08.2023 13:57
      +5

      Вот что за мания ключевые слова сокращать? Что авторы экономят? Читаемость кода так улучшают.


      1. Acuna
        07.08.2023 13:57

        Не знают же об autocomplete, а ручками писать лень)))


  1. simenoff
    07.08.2023 13:57

    Как дела у Zig с GUI-разработкой?


    1. AnimeSlave Автор
      07.08.2023 13:57
      +1

      Я пробовал на нём GTK. И оно работало. IUP есть. Удобство, что можно привязать любой проект на C


  1. SergeiMinaev
    07.08.2023 13:57
    +6

    Я как-то тоже решил поискать современный аналог Си, но без лишних наворотов. Zig, наверное, единственный адекватный кандидат.

    Проникся уважением к автору Zig, когда посмотрел это видео - https://www.youtube.com/watch?v=Z4oYSByyRak . ИМХО, грамотно рассуждает и зрит в корень.


  1. diakin
    07.08.2023 13:57
    +1

    const std = @import("std");
    const raylib = @import("raylib");
    const ResourceManager = @import("resourcemanager.zig").ResourceManager;
    const ScreenManager = @import("screenmanager.zig").ScreenManager;
    
    const allocator = std.heap.c_allocator;
    
    pub fn main() !void {
        const screen_width = 800;
        const screen_height = 450;
    
        raylib.Window.init(screen_width, screen_height, "Test Window");
        defer raylib.Window.close();
    
        raylib.AudioDevice.init();
        defer raylib.AudioDevice.close();
    
        var resouce_manager = try ResourceManager.init(allocator);
        defer resouce_manager.deinit();

    Да можно другую подсветку включить )


  1. JustForFun88
    07.08.2023 13:57
    +10

    Не знаю, как по мне Zig недалеко ушёл от C. Он не предлагает ничего лучшего чем сам C.

    Все также необходимо быть внимательным и следить за висячими ссылками, нулевыми указателями.

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

    А какая головная боль с асинхронным кодом в C. Zig это решает? Да нет, все тоже самое.

    Зачем такой язык нужен, если не предлагает ничего координально нового и самое главное легкого? Легкий синтаксис не облегчает знаете ли написание асинхронного кода.

    В этом плане уж лучше Rust. Да немного громоздкий синтаксис. Зато как спокойно ????. Прям закачаешься ????


    1. AnimeSlave Автор
      07.08.2023 13:57

      Весь ваш комментарий далёк от реальности. И я не буду спорить о том, что чего-то нет в самом языке, и это факт, что чего-то нет. Глупо ставить на этом претензию к языку, так как он ещё на пути разработки. Сделаю только ремарку, что более удобная асинхронность в планах на версию 0.12.0


      1. JustForFun88
        07.08.2023 13:57
        +1

        Эта удобная асинхронность включает в себя я так понимаю просто привнесение async/await/suspend/resume.

        Каким образом это исключает гонку данных и необходимость следить за висячими указателями?


    1. Helltraitor
      07.08.2023 13:57
      +4

      Я только не понимаю, почему все считают синтаксис Rust сложным? Единственное, чем у меня была проблема - турбофиш


      1. JustForFun88
        07.08.2023 13:57
        +1

        Со временем привыкаешь)) Я вот втянулся.
        Он не сложный, просто немного громоздкий что ли? ????


        1. Helltraitor
          07.08.2023 13:57
          +1

          Он обычный, си-подобный. Лямбды мне из C++ больше заходят, но в Rust они компактные. В остальном вообще ничего сказать не могу. В чем громоздкость - хз


  1. Refridgerator
    07.08.2023 13:57

    А что там с IDE?


    1. AnimeSlave Автор
      07.08.2023 13:57

      Официальной IDE нет. Вроде как и не рассматривается. Можно использовать VSCode. И для ряда текстовых редакторов есть плагины. Более подробнее по ссылке: https://ziglang.org/learn/tools/

      У IntelliJ IDEA есть плагин ещё https://plugins.jetbrains.com/plugin/10560-zig


  1. Panzerschrek
    07.08.2023 13:57
    +1

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

    Что касается ZIG: выглядит как проект неосиляторов более серьёзных языков вроде C++ и Rust. Отсутствие деструкторов ставит крест на надёжности зарабатываемых программ, т. к. даёт широчайшие возможности забыть что-то освободить (отдать память, закрыть файл и т. д.). Причина выбjра такого подхода рациональному разуму не поддаётся - тут только приходит на ум некий иррациональный страх над (мнимой) сложностью C++.


    1. IvaYan
      07.08.2023 13:57

      это ядро Linux и за C++ код тов. Торвальдс там матом кроет

      А вот Rust как раз можно :)


    1. SpiderEkb
      07.08.2023 13:57
      +1

      Причина выбjра такого подхода рациональному разуму не поддаётся - тут только приходит на ум некий иррациональный страх над (мнимой) сложностью C++.

      Причины вполне понятны. Дело в том, что на С пишут "достаточно специфические люди". Которые примерно знают во что превратится их код на ассемблере (не до строчки, но примерено представляют). И для них это критично (ресурсы, быстродействие и т.п.).

      Когда ты пишешь на С++, ты никогда не знаешь во что выльется твой код. Что стоит за вызовом той или иной функции - сколько там будет динамических выделений/освобождений памяти, сколько создастся/схлопнется уровней стека и т.п. А в ряде ситуаций это достаточно критично (и сам не раз с этим сталкивался когда ++-й код не проходит по быстродействию, а написанное на чистом С проходит, или по ресурсам когда с нагрузочном тестировании тебе вываливают PEX статистику с претензией что динамическая работа с памятью в плюсовых либах отжирает 2-3% ресурсов процессора).

      В этом плане С и С++ радикально разные языки. И для некоторых вещей, где традиционно используется С, С++ не подходит (ну или надо писать на нем в очень специфическом стиле).


      1. AnthonyMikh
        07.08.2023 13:57

        Дело в том, что на С пишут "достаточно специфические люди". Которые примерно знают во что превратится их код на ассемблере (не до строчки, но примерено представляют).

        Ага, а потом очень обижаются, когда их столетний говнокод перестаёт работать из-за того, что новая версия компилятора начинает абузить UB в их коде.


        И да, они почти наверняка не знают.


        Когда ты пишешь на С++, ты никогда не знаешь во что выльется твой код. Что стоит за вызовом той или иной функции — сколько там будет динамических выделений/освобождений памяти, сколько создастся/схлопнется уровней стека и т.п

        Надо же, совсем как в C!


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

        Это очень странная ситуация, потому что (почти) всё, что можно написать на C, можно написать и на C++. И, к слову, C++ не заставляет использовать динамическую аллокацию памяти.


  1. punzik
    07.08.2023 13:57

    Конструкция с блоком и break похожа на продолжения.


    1. AnimeSlave Автор
      07.08.2023 13:57

      Вы правы, но всё несколько сложнее, так как эту конструкцию можно использовать в циклах для перехода между вложенными циклами. И там, да, есть и continue. Пример из документации:

      test "nested continue" {
          var i: usize = 0;
          outer: while (i < 10) : (i += 1) {
              while (true) {
                  continue :outer;
              }
          }
      }
      


      1. punzik
        07.08.2023 13:57

        Я имет в виду continuation, механизм сохранения/восстановления контекста из мира ФП.


        1. AnimeSlave Автор
          07.08.2023 13:57

          Не сталкивался. По документации в Zig это не оно


          1. punzik
            07.08.2023 13:57
            +1

            Да, просто похоже на один из вариантов применения продолжений. Это я к тому, что конструкция понятная.


  1. pvvv
    07.08.2023 13:57
    +2

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

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

    https://github.com/tsoding/nobuild


    1. AnimeSlave Автор
      07.08.2023 13:57

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

      Всегда есть место для улучшений. Я с недавних пор вместо cmake стал чаще применять meson. Что то, что это - «Аксиома Эскобара», но Meson, как-то адекватнее выглядит в этом плане.

      За ссылку спасибо. Унёс себе в закрома


  1. SysManOne
    07.08.2023 13:57
    +1

    Осталось за кадром вот что: что же всё таки лучше в этом новоязе чем в Си ? Ну допустим вам значок "@" понравился - ладно (мне например нравиться "$" вкорячивать, а других тошнит, ну и по фигу) - а где описание мук и проблемы выбора от автора, что заставили (таки заставили) уйти с накопившего многодесятилетние знания языка на новояз, да ещё с неясным будущим.
    Вы сделает пару проектов, и свалите (пардон) - вновь прибывшим останется такое вот наследство. По-моему это совсем не инженерный подход и, скорее, будет подлянкой использовать такое средство - или этот фактор с умыслом, намеренно оставлен за скобками ?


    1. AnimeSlave Автор
      07.08.2023 13:57
      +1

      что же всё таки лучше в этом новоязе чем в Си ?

      Вы это спрашиваете как C программист? Потому что проблемы языка C известны. Статья, которую я написал лишь описывает, что мне понравилось, а не весь язык в целом. Я в первом комментарии написал причину, которой не стал делать полный обзор языка.

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


      1. SysManOne
        07.08.2023 13:57
        -1

        Вопросом на вопрос ? Нет проблем у языка Си большем чем у других. Есть уровень владения средством.
        Повидал много диалектов Си, всё было "клёво" и занятно - и настолько же бестолково и сиюминутно. Вот природа вопроса.

        Чем вам поможет если я отвечу что спрашиваю как допустим BLISS-пограмиила или MACRO и FORTRAN или Си?

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


        1. AnimeSlave Автор
          07.08.2023 13:57
          +1

          Из того что вам понравилось я таки не понял как на этом новоязе шлёпать драйверы

          Точно также как на C. Логика языка Zig невилировать недостатки языка C. Сделать взаимодействие с ним проще. Мне лично этого было достаточно, чтобы попробовать с Zig поработать. И на данный момент меня он устраивает. Он мне нравится больше


          1. SpiderEkb
            07.08.2023 13:57
            +2

            Логика языка Zig невилировать недостатки языка C. Сделать взаимодействие с ним проще.

            Ну вот как пример. Я сейчас много пишу на языке RPG (до это около 25 лет на С/С++, причем, больше в сторону С).

            Ни в коей мере не конкурирует с С т.к. это язык с уклоном в реализацию бизнес-логики. Но. Там есть вещи, которых мне (когда нужно что-то делать на С) хотелось бы видеть в С.

            Логика работы со структурами.

            В RPG структура рассматривается как некий блок данных, внутри которого определены поля. Простейший случай ровно как в С:

            dcl-ds dsMyStruct;
              intField   int(10);
              charField  char(50);
            end-ds;

            Тут все просто. Но на самом деле все намного гибче. Можно задавать размер структуры явно и явно указывать положение поля внутри структуры:

            dcl-ds dsMyStruct len(512);
              intField   int(10)  pos(50);
              charField  char(50) pos(150);
            end-ds;

            Тут мы имеем блок 512 байт в котором нас интересует только целое в 50-й позиции и строка в 150-й, остальное не наше дело.

            Поля могут накладываться друг на друга. Как union в С, но без union, просто накладываем и все

            dcl-ds dsMyStruct;
              Field1  char(50);
              Field2  uns(3) dim(50) samepos(Field1);
            end-ds;

            Наложили на строку 50 символов массив из 50-ти баззнаковых байт.

            Причем, наложение может быть с пересечением - не проблема

            dcl-ds dsMyStruct;
              Field1  char(20);
              Field2  char(20);
              Field3  char(20) pos(11);
            end-ds;

            Третье поле перекрывает вторую половину первого и первую половину второго.

            Для массивов внутри структур еще хитрее

            dcl-ds dsMyStruct;
              arrA    char(10)  dim(10);
                arrB  char(5)   overlay(arrA);
                arrC  char(5)   overlay(arrA: *next);
            end-ds;

            Тут описан массив А - 10 элементов по 10 символов. Но каждый символ состоит из двух частей по 5 символов. Массивы В и С позволяют непосредственно обращаться к каждой из частей. Например:

            arrA(1) = 'abcdefghij';
            arrA(2) = 'klmnopqrts';

            и тогда

            arrB(1) = 'abcde';
            arrC(1) = 'fghij';
            arrB(2) = 'klmno';
            arrC(2) = 'pqrts';

            И все это без лишних сущностей, просто в рамках описания структуры.

            При описании структуры можно сразу задавать начальные значения полей (инициализация). В том числе и в том случае, когда структура описывается как шаблон (аналог typedef в С)

            dcl-ds t_dsMyStruct template;
              intField   int(10)   inz(350);
              charField  char(50)  inz('Initial string');
            end-ds;

            Тут описан шаблон (без создания переменной) структуры с инициализированными полями. Чтобы создать переменную соответствующей структуры

            dcl-ds dsMyStruct likeds(t_dsMyStruct) inz(*likeds);

            Создается структура "такая же как" и инициализируется "так же как".

            В структурах можно использовать неименованные поля. Вместо всяких reserved, tmp, dummy и т.п.

            dcl-ds t_dsISODate qualified template;
              YYYY    char(4) inz('2000');
              *n      char(1) inz('-');  // разделитель
              MM      char(2) inz('01');
              *n      char(1) inz('-');  // разделитель
              DD      char(2) inz('01');
              strDate char(10) pos(1);   // дата в виде строки
            end-ds;
            
            // После такого объявления dsISODate.strDate = '2000-01-01'
            dcl-ds dsISODate likeds(t_dsISODate) inz(*likeds);
            
            // А после этого dsISODate.strDate = '2023-08-09'
            dsISODate.YYYY = '2023';
            dsISODate.MM   = '08';
            dsISODate.DD   = '09';

            Все это позволяет достаточно просто описывать достаточно сложные структуры данных.

            Далее. Переменные. Здесь не может быть неинициализированных переменных. Любая объявленная переменная при компиляции инициализируется дефолтным для ее типа значением, если не указано иного.

            Есть два оператора:

            clear <varName>;

            Сброс значения переменной к дефолтному для ее типа.

            И

            reset <varName>;

            Сброс значения переменной к значению указанному при инициализации.

            // тут intVar1 = 0, intVar2 = 15
            dcl-s intVar1  int(10);
            dcl-s intVar2  int(10) inz(15);
            
            // тут intVar1 = 100, intVar2 = 150
            intVar1 = 100;
            intVar2 = 150;
            
            // тут intVar1 = 0, intVar2 = 0
            clear intVar1;
            clear intVar2;
            
            // тут intVar1 = 150, intVar2 = 50
            intVar1 = 150;
            intVar2 = 50;
            
            // тут intVar1 = 0, intVar2 = 15
            reset intVar1;
            reset intVar2;

            Для переменных, которые были инициализированы по умолчанию, reset тождественен clear

            "Спецзначения". Их много всяких, начинаются с '*' - *next, *var, *auto и т.п. В целом просто синтаксис языка, но особо хотелось бы отметь два: *loval и *hival - минимально и максимально возможные значения для данного типа переменной. Не надо думать где и как описано максимально/минимальное значение для данного типа - просто ставим *hival/*loval - компилятор сам разберется

            // packed - формат данных с фиксированной точкой
            // в данном случае - 15 знаков, без десятичных для результата
            // и 63 без десятичных для промежуточных вычислений
            dcl-s calcValue packed(63: 0);
            dcl-s resValue  packed(15: 0);
            // максимальные и минимальные значения для resValue
            // тут еще один сахарок - объявляем переменную "такую же как"
            dcl-s maxValue  like(resValue) inz(*hival);
            dcl-s minValue  like(resValue) inz(*loval);
            
            // делаем какие-то промежуточные вычисления
            calcValue = ...
            
            // размерность calcValue больше чем resValue 
            // поэтому напрямую присваивать нельзя - может быть переполнение
            // безопасно - с контролем выхода за границы допустимых значений
            select;
              when calcValue < minValue;
                resValue = minValue;
            
              when calcValue > maxValue;
                resValue = maxValue;
            
              other;
                resValue = calcValue;
            endsl;

            Тут суть в том, что для calcValue диапазон значений от -9999...999 (63 девятки) до 9999...999 (63 девятки), а для resValue - от -9999...999 (15 девяток) до 9999...999 (15 девяток). Поэтому объявляем переменные с граничными значениями "такие же как" resValue и инициализируем их минимальным и максимальным значениями именно для packed(15: 0). И с ними сравниваем промежуточный результат.

            Будь подобное (структуры, инициализация, спецзначения...) реализовано в С (или ему подобном языке), читаемость кода (да и удобство) было бы выше.


            1. AnimeSlave Автор
              07.08.2023 13:57
              +1

              Такое бы хорошо отдельной статьей. Интересные вещи

              Тут мы имеем блок 512 байт в котором нас интересует только целое в 50-й позиции и строка в 150-й, остальное не наше дело.

              Такое, кстати, есть в Zig. packed struct. Можно указать тип данных под которые он выравнивается. Очередной пример из моего эксперимента с raylib:

              pub const Config = struct {
                  /// System/Window config flags
                  /// NOTE: Every bit registers one state (use it with bit masks)
                  /// By default all flags are set to 0
                  pub const Flags = packed struct(u16) {
                      _: bool = false, //0
                      fullscreen_mode: bool = false, //2,
                      window_resizable: bool = false, //4,
                      window_undecorated: bool = false, //8,
                      window_transparent: bool = false, //16,
                      msaa_4x_hint: bool = false, //32,
                      vsync_hint: bool = false, //64,
                      window_hidden: bool = false, //128,
                      window_always_run: bool = false, //256,
                      window_minimized: bool = false, //512,
                      window_maximized: bool = false, //1024,
                      window_unfocused: bool = false, //2048,
                      window_topmost: bool = false, //4096,
                      window_highdpi: bool = false, //8192,
                      window_mouse_passthrough: bool = false, //16384,
                      interlaced_hint: bool = false, //65536,
              
                      pub fn to_u16(self: Flags) u16 {
                          return @bitCast(self);
                      }
                  };
              
                  /// Setup init configuration flags (view FLAGS)
                  pub fn setFlags(flags: Flags) void {
                      SetConfigFlags(@as(c_uint, flags.to_u16()));
                  }
              };
              

              И потом можно переназначить отдельные биты:

              raylib.Config.setFlags(.{
                  .vsync_hint = true,
                  .window_resizable = true,
              });
              


              1. SpiderEkb
                07.08.2023 13:57
                +1

                Такое бы хорошо отдельной статьей. Интересные вещи

                На отдельную статью терпения не хватит :-) Там очень много всякого "сахара". Я тут просто обозначил то, что было бы уместно в том же С и не противоречит его идеологии.

                Такое, кстати, есть в Zig. packed struct. Можно указать тип данных под которые он выравнивается

                Это не совсем то. Выравнивание это выравнивание. Битовые поля - это бытовые поля.

                В RPG все структуры по умолчанию packed - без выравнивания (точнее, с выравниванием на байт). Если нужно выравнивание - нужно добавлять align в описание структуры.

                Но позиционирование полей - это не выравнивание. Это именно указание на то, в каком месте структуры расположено данное поле. В С такого нет. Это можно реализовать через указатели на какое-то место внутри структуры с заданным смещением от ее начала, но только в рантайме. А в RPG это делается в compile time просто на уровне описания. И такой подход делает избыточным такую сущность как union - все это реализуется и без этого.

                // C
                typedef union uINTasBYTES {
                  unsigned int  i;
                  unsigned char ch[4];
                };
                
                // RPG
                dcl-ds t_dsINTasBYTES qualified template
                  i    uns(10);
                  ch   uns(3) dim(4) samepos(i);
                end-ds;

                ровно тоже самое, но в RPG в эту же структуру можем еще много чего напихать, а в С придется отдельно описывать union, отдельно структуру куда он входит. А если надо частичное перекрытие двух полей третьим, то жаде сходу не придумаю как это в С реализовать на уровне описаний, только в рантайме через указатели.

                Аналогично для массивов с overlay - сходу не придумаю как это просто и лаконично в С описать. Только через вложенные структуры.


            1. SysManOne
              07.08.2023 13:57

              dcl-ds dsMyStruct; Field1 char(50); Field2 uns(3) dim(50) samepos(Field1); end-ds;

              Отслеживание блоков визуально по вайтспейсам - что ещё может быть корявей , когда оступы в каждой деревне свои ? :-)


          1. SysManOne
            07.08.2023 13:57

            Ну, "устраивает" - так устраивает. По мне так эта фанская мишура, конкуренции с Си-ми не выдержит как не выдержали специализированные же языки для системного программирования, к сожалению .
            Я не фан Сей, пишу на нём 30+ лет, но есть ряд сугубо инженерных и технологических факторов, не разрешив которе в новоязе - заменить кирзовую Си-шку не удасться. Размножение же сущего (тот же ЯВУ 3Ж, но с кравлениями чего-то стороннего) претит технологическим канонам.


            1. AnimeSlave Автор
              07.08.2023 13:57

              По мне так эта фанская мишура

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


              1. SysManOne
                07.08.2023 13:57

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

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

                A, B, Си вырос как раз из "фанской мишуры" и победил кокурентов по итогам почти 10+ летнего противостояния, развив технологию применения и сформировав инженерные кадры.
                Но, прикиньте если "выстрелит" и мегатонные Си-легаси потащють на нечто новое. Будьте аккуратны в своих мечтаниях. :-)


  1. kekoz
    07.08.2023 13:57
    +1

    Когда-то я, наблюдая развитие C++, питал похожие надежды на перспективы D. Ему сегодня уже третий десяток лет идёт. Где он?

    Официально Zig младше Rust менее, чем на год. То есть, практически ровесники.

    Последний сегодня принят сообществом Linux, принят в Microsoft, принят в Google, можно продолжать.

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

    Положа руку на сердце, не слишком много оснований для веры в большое будущее.


    1. AnimeSlave Автор
      07.08.2023 13:57
      +1

      Когда-то я, наблюдая развитие C++, питал похожие надежды на перспективы D. Ему сегодня уже третий десяток лет идёт. Где он?

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

      Официально Zig младше Rust менее, чем на год. То есть, практически ровесники.

      2005 и 2015? Один год? Если уж брать ваше допущение у rust на старте была команда и инвестиции, а у zig это произошло совсем недавно. Очень странно сравнивать уже устаявщийся язык и только развивающийся. И при этом тут же писать, что новый язык не стабилизирован. По объектимным причинам он ещё не стабилизирован


      1. Siemargl
        07.08.2023 13:57

        Проблему со сборщиком мусора, т.е возможность писать без GC в D решили. Но очень уж поздно и до сих пор режим BetterC на роли падчерицы с минимумом внимания.

        Также как и с IDE и еще с многими вещами, которые хотело сообщество.

        В итоге сообщество прошло мимо, да и корпорации тоже (но те просто тянут на себя - NIH).


    1. Refridgerator
      07.08.2023 13:57

      Проблема D была в том, что он сочинялся как «убийца С++» вместо как «удобный язык для людей», и при этом там и не смог избавиться от наследия си в синтаксисе. Что-то убавилось, что-то добавилось, а потом внезапно раз — и в новой версии компилятора старые примеры из туториала уже не компилируются. Что это значит? Это значит, что авторы языка с самого начала крайне смутно понимали, что должно получится в результате. Ну добавили они поддержку комплексных чисел на уровне языка, круто же, киллер-фича типа. Вот только зачем они нужны программисту авторы не понимали, поэтому никакой инфраструктуры для их использования нет. И в остальном как-то так же.

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


      1. SpiderEkb
        07.08.2023 13:57
        +2

        Если за языком не стоит большая корпорация — шансов пробиться в массы у него ноль

        А даже если и стоит. Достаточно велика вероятность того, что он останется внутри этой корпорации.

        Вот RPG, который я постоянно упоминаю. За ним стоит IBM. Достаточно большая корпорация. Существует он с конца 50-х, фактически ровесник COBOL. Но при этом до сих пор развивается. Тот RPG? на котором пишем сейчас и тот, что был в начале - фактически два разных языка.

        То, что было когда-то:

             D DS1             DS                  QUALIFIED DIM(2)                
             D subfld1                       10                                    
             D subfld2                        5                                    
             D subfld3                        5                                    
                                                                                   
                                                                                   
             C                   EVAL      DS1(1).subfld1 = 'DS1_1'                
             C                   EVAL      DS1(1).subfld2 = 'DS1_2'                
             C                   EVAL      DS1(1).subfld3 = 'DS1_3'                
                                                                                   
             C     ds1(1)        DSPLY                                             
             C                   EVAL      DS1(2).subfld1 = 'DS1_4'                
             C                   EVAL      DS1(2).subfld2 = 'DS1_5'                
             C                   EVAL      DS1(2).subfld3 = 'DS1_6'                
             C     ds1(2)        DSPLY                                             
             C                   SETON                                        LR    

        И то же самое сейчас:

        dcl-ds ds1 qualified dim(2);  
         subfld1 char(10);            
         subfld2 char(5);             
         subfld3 char(5);             
        end-ds;   
        
        DS1(1).subfld1 = 'DS1_1';     
        DS1(1).subfld2 = 'DS1_2';     
        DS1(1).subfld3 = 'DS1_3';     
        dsply ds1(1);                 
                                      
        DS1(2).subfld1 = 'DS1_4';     
        DS1(2).subfld2 = 'DS1_5';     
        DS1(2).subfld3 = 'DS1_6';     
        dsply ds1(2);                 
                                      
        *INLR = *ON;                   

        RPG был реализован на платформах Digital VAX, Sperry Univac BC/7, Univac system 80, Siemens BS2000, Burroughs B1700, Hewlett Packard HP3000, ICL 2900 series, Honeywell 6220, WANG VS, IBM PC (DOS).

        Компилятор Visual RPG, разработанный сторонним производителем, обеспечивает работу под Windows и поддержку GUI. Существуют также реализации для OpenVMS и других, более экзотических платформ.

        Это не считая того, что он поддерживался в VisualAge.

        Но на данный момент этот язык значим только в рамках IBM'овских платформ IBM i (middleware, потомок AS/400, которая в свою очередь выросла из System/38) и IBM z (mainframe, потомок System/370).

        Там он живее всех живых - более 80% кода на IBM i написано и пишется на RPG. И в каждой новой версии ОС (и новых TR - technical refresh) появляются какие-то новые языковые фички.

        Но за пределами этих платформ он практически не известен и широкого распространения не получил.

        Второй момент, как мне кажется, ошибочный - все стараются сделать "универсальный язык на все случаи жизни". Этакую серебряную пулю для всех. Что концептуально неверно и ведет к излишнему усложнению языка и, как следствие, росту количества внутренних противоречий в нем.

        На мой взгляд более продуктивно было бы для каждого языка поддерживать свою направленность, а универсальности добиваться за счет возможности "стыковки" языков между собой. Та ж IBM реализует это в рамках концепции ILE (Integrated Language Environment), на других платформах нечто подобное реализуется в LLVM. Суть подхода в том, что разработчик может решать разные части одной задачи на разных языках - каждую часть на том языке, который наиболее эффективен для ее решения. А затем объединять несколько модулей в один программный объект.

        Примерно так мы и работаем - сложная бизнес-логика пишется на RPG, но если видишь что на С/С++ что-то написать проще и эффективнее (например, какое-то низкоуровневое взаимодействие с системой) - пишем на С/С++. И тот и другой язык имеют свои плюсы и минусы, каждый в своей нише, и прекрасно стыкуются между собой на уровне модулей (некий аналог объектного файла) которые можно биндить (линковать) в один программный объект (программу). Как пример:

        Программа из несколкьких модулей, в основном на RPG. Но вот оказалось что функцию

        typedef struct tagPASSPORT {
          char Parsed;
          char Passport[INPUT_LEN];
          char Type[TYPE_LEN];
          char Series[SER_LEN];
          char Number[NUM_LEN];
          char Emitent[OFFICE_LEN];
          char EmissionDate[DATE_LEN];
        };
        
        typedef tagPASSPORT* PPASSPORT;
        
        extern "C" char ParsePassport(PPASSPORT pPassport)

        Проще написать на С, объявить ее прототип на RPG

              dcl-ds t_Passport qualified template;
                Parsed        char(1)    inz('N');
                Passport      char(350)  inz(*blanks);
                *n            char(1)    inz(x'00');
                Type          char(15)   inz(*blanks);
                Series        char(10)   inz(*blanks);
                Number        char(50)   inz(*blanks);
                SerialNumber  char(50)   inz(*blanks);
                Emitent       char(300)  inz(*blanks);
                EmissionDate  char(10)   inz(*blanks);
              end-ds;
        
        
               dcl-pr ParsePassport ind extproc(*CWIDEN : 'ParsePassport');
                 Passport   likeds(t_Passport);
               end-pr;

        И вызывать из RPG кода, включив в программу модуль PARSEPASSP.CPP


        1. Refridgerator
          07.08.2023 13:57

          А даже если и стоит. Достаточно велика вероятность того, что он останется внутри этой корпорации… RPG
          Ну тут-то всё логично: «Синтаксис RPG был изначально сходен с командным языком механических табуляторов компании IBM. Был разработан для облегчения перехода инженеров, обслуживавших эти табуляторы, на новую технику и переноса данных». Давно уже нет ни табуляторов этих, ни инженеров, некому больше облегчать процесс перехода. А куча написанного кода — есть, только за счёт этого и держится.

          Второй момент, как мне кажется, ошибочный — все стараются сделать «универсальный язык на все случаи жизни». Этакую серебряную пулю для всех.
          А ошибочный он потому что — есть уже такие языки. Никому не нужен ещё один С++.


          1. SpiderEkb
            07.08.2023 13:57

            А ошибочный он потому что — есть уже такие языки.

            Например?

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

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

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

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


            1. Refridgerator
              07.08.2023 13:57

              Например?
              C/C++, более универсальных языков я не знаю.

              В каком языке есть нативная (на за счет внешних библиотек) поддержка типов с фиксированной точкой, поддержка комплексных чисел, поддержка специальных функций и т.д. и т.п.?
              Так а почему она должна быть именно нативная? Типов данных сколько угодно может быть, нельзя заранее все их учесть. Про комплексные числа знают многие, а вот про дуальные — уже нет, хотя в вычислениях они ничуть не менее полезны. В С++ можно сделать абстрактную обёртку над ними и оперировать так, как будто они поддерживаются нативно (ну или близко к тому) точно так же, как и с комплексными, на равных. А в вот в Mathematica, в чисто математическом языке казалось бы, такой фокус уже не пройдёт — там всё уже заточено под комплексные числа и все прочие реализуются через костыли.

              Или например в С++ я смог построить такую систему типов чтобы можно писать
              if(1<x<5) {...}
              Удобно и наглядно. А в вот в Mathematica, специализированном языке — увы нет.


              1. SpiderEkb
                07.08.2023 13:57

                C/C++, более универсальных языков я не знаю.

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

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

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

                Вот коммерческая бизнес-логика. Все расчеты ведутся с использованием типов данных с фиксированной точкой. В С/С++ их нет. Значит вам придется потратить время и дописать самому поддержку этих типов + арифметику для работы с ними (всякие там операции без округления, операции с округлением и т.п.).

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

                Потом в RPG есть возможность напрямую работать с таблицами БД (и есть случаи когда это реально эффективнее). В С/С++ вам и это придется дописывать.

                В RPG есть возможность инкапсуляции SQL в код с использованием хост-переменных. В С/С++ это тоже придется делать руками.

                В результате прежде чем написать что-то такое, что на RPG пишется за день-два, в С/С++ вы потратите кучу времени на создание нужного инструментария.

                Иными словами - натягивать сову на глобус.

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

                Тут ровно тоже самое. Есть типовые задачи бизнес-логики и есть инструмент (RPG), который заточен под их решение. Там где этого инструмента не хватит, я просто возьму другой (С/С++) и решу часть задачи на нем. В результате каждый кусок я реализую теми средствами, которыми это делается максимально быстро и удобно.

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

                В С/С++, например, не нравится концепция структур. Она слишком узкая и для описания каких-то сложных структур требует дополнительных сущностей типа юниона или вообще ухода в классы где многое нужно делать в рантайме с тратой процессорных ресурсов.

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

                И плюсом, если мне не хватает чего-то в RPG я могу из RPG кода вызвать любую функцию из С-шных библиотек просто описав ее прототип на RPG.

                Я давно уже вырос из того, чтобы рассуждать о том, какой язык плохой, а какой хороший. Я с 91-го года в разработке и писал много на чем (и фортран и С/С++, всякие фоксы-парадоксы, кларион и даже на прологе немного, сейчас вот еще RPG...) И меня совершенно не ставит в тупик необходимость освоить что-то еще, если того требует задача.

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

                Если, конечно, ты хочешь решать задачи максимально быстро и максимально эффективно.


  1. DerRotBaron
    07.08.2023 13:57

    Не увидел ответа на главную главную проблему C: UB.
    Без решения этой проблемы он не нужен и не менее вреден, чем C, с ним вполне кандидат на роль "убийцы"


  1. KMiNT21
    07.08.2023 13:57

    Очень странно, что ни слова нет про NIM (язык "счастья") в контексте такой статьи. Можно сделать вывод, что автор его упустил из виду. :)


    1. AnimeSlave Автор
      07.08.2023 13:57

      Нет. Не упустил. Забыл о нём. Он один из тех языков, которых я изучал, когда искал альтернативы C. С ним банально просто. Синтаксис не мой. Много функционального подхода в синтаксисе. Я после ruby не люблю такой подход в программировании


  1. starfair
    07.08.2023 13:57

    Автор, всё хорошо , но какая энумерация? Если уж вы пишите русские наименования сиречь, переводы, то есть же прекрасное и понятное "перечисление". Режет глаз, честно говоря. Тут надо или по русски, или по английски. Это не только к вам конечно, но вообще, очень напрягает последнее время всякое типа: активности, энумерации, корутины и прочее. Причем, и единого то стандарта нет, а каждый автор своё придумывает, и в итоге чтение статьи превращается в головную боль иногда.