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

Status Quo

Большинство современных языков программирования строятся на одной из трех ссылочных моделей:

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

Во вторую категорию попадают языки с подсчетом ссылок. Это Оbjective-C, Swift, Частично Rust, C++ при использовании умных указателей и некоторые другие. Эти языки позволяют автоматизировать до некоторой степени удаление ненужных объектов. Но это имеет свою цену. В многопоточный среде такие счетчики ссылок должны быть атомарными, а это дорого. К тому же, подсчет ссылок не может освободить все виды мусора. Когда объект А ссылается на объект Б а объект Б обратно ссылается на объект А такая закольцованная иерархия не может быть удалена подсчетом ссылок. Такие языки как Rust, Swift вводят дополнительные не владеющие ссылки которые решают проблему закольцовок ценой усложнения объектной модели и синтаксиса.

В третью категорию попадают большинство современных языков программирования. Это языки с автоматической сборкой мусора: Java, JavaScript, Kotlin, Python, Lua... В этих языках ненужные объекты удаляются автоматически, но есть нюанс. Сборщик мусора потребляет очень много памяти и процессорного времени. Он включается в случайные моменты времени и ставит основную программу на паузу. Иногда полностью — на все свое время работы, иногда частично. Сборки мусора без пауз не существует. Гарантию сборки всего мусора может дать только алгоритм который просматривает всю память и останавливает приложение на все свое время работы. В реальной жизни такие сборщики давно не используются ввиду своей неэффективности. В современных системах некоторые мусорные объекты не удаляются вообще.

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

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

То есть проблемы есть и текущие методы их решения имеют изъяны.

Возможное решение

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

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

  • Ассоциация — когда один объект знает о другом. Ассоциация не предполагает владения.

  • Композиция — когда объект монопольно владеет другим объектом. Например, колесо может находиться одновременно только в одной машине.

  • Агрегация это множественное владение. Когда например много человек шарят имя Андрей. 

Разберем это на более конкретных примерах:

База данных владеет своими таблицами, вьюхами, нумераторами и хранимыми процедурами. Таблица владеет своими записями, метаданными колонок и индексами. Запись владеет своими полями. Другой пример: форма пользовательского интерфейса владеет своими контролами, список владеет своими элементами. Документ владеет своими таблицами стилей страницами которые в свою очередь владеют элементами на странице текстовые блоки владеют абзацами которые владеют символами. Все эти отношения — это композиция. Композиция всегда формирует древовидную структуру, в которой у каждого объекта есть ровно один владелец, и объект существует только до тех пор, пока этот владелец на него ссылается. Мы всегда знаем, когда объект нужно удалить, то есть сборщик мусора для таких ссылок не нужен, как не нужен и их подсчет.

Примеры ассоциации: абзацы документов ссылаются на стили, одни записи таблиц ссылаются на другие записи других таблиц (допустим что у нас продвинутая реляционная база данных, в которой такие связи кодируются специальным типом данных, а не с помощью foreign keys), элементы управления на GUI-форме связаны в цепочку перехода по клавише Tab, control на форме ссылается на модель данных, а она в свою очередь ссылается на control в каком-нибудь реактивном приложении. Все эти связи обеспечиваются не владеющими указателями. Они не препятствуют удалению объекта, но обязаны как-то обрабатывать это удаление, чтобы обеспечить memory safety, а язык должен пресекать попытки доступа по таким ссылкам без проверки на потерю объекта.

Агрегация штука опасная. Весь опыт индустрии говорит о том что агрегировать можно только неизменяемые объекты. Например неизменяемыми являются строки в Java, поэтому множество объектов может ссылаться на одну и ту же строку. Если объект имеет множество владельцев из разных иерархий, его изменение приведет к самым печальным последствиям в самых неожиданных местах. Поэтому язык программирования должен пресекать агрегирование изменяемого объекта. Можно ли исключить использование агрегации полностью как то рекомендуют соглашение о стиле  кодирования Гугла? Это было бы слишком радикально. Например, популярный паттерн проектирования flyweight построен именно на агрегации. К тому же агрегат здорово поможет в многопоточной среде, где неизменяемые объекты могут безопасно шариться между потоками.

Интересно, что иерархия неизменяемых объектов связанных агрегирующими ссылками не может содержать закольцовок. Каждый неизменяемый объект начинает свою жизнь как изменяемый, так как объект нужно заполнить данными и только потом "заморозить", сделав неизменяемым. А не изменяемые объекты, как мы уже убедились, не могут содержать закольцовок. Следовательно закольцовки в правильно организованных иерархиях объектов могут происходить только по не-владеющим ассоциативным ссылкам. Владеющие ссылки композитов всегда формируют дерево. Агрегирующие ссылки — направленный ациклический граф (DAG). И ни одна из этих структур не нуждается в сборщике мусора.

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

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

  • на декларативном уровне поддерживать UML-ссылки

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

  • на этапе компиляции инфорсить правила использования этих ссылок (один владелец, константность, проверка на потерю объекта и т.д.),

...то такой язык обеспечит и memory safety, и отсутствие утечек памяти, и отсутствие оверхедов сборщика мусора, и существенно упростит жизнь программиста. А поскольку объекты будут удаляться в предсказуемые моменты времени, это позволит присоединить управление ресурсами к времени жизни объектов.

Реализация

В экспериментальном языке программирования Argentum идея UML-ссылок реализована так:

  • Поле класса, помеченное "&" является не владеющей ссылкой (ассоциацией).

  • Поле помеченное "*" является разделяемой (шареной) ссылкой на неизменяемый объект (аггрегацией)

  • Все прочие поля-ссылки это композиция (такое поле — единственный владелец изменяемого объекта).

Пример:

class Scene {
   // Поле `elements` владеет объектом `Array`, который владеет множеством `SceneItem`s
   // Это композиция
   elements = Array(SceneItem);

   // Поле `focused` ссылается на произвольный `SceneItem`. Без владения
   // Это ассоциация
   focused = &SceneItem;
}
interface SceneItem { ... }
class Style { ... }
class Label { +SceneItem;  // Наследование
    text = "";  // Композиция: строка принадлежит лейблу

    // Поле `style` ссылается на неизменяемый экземпляр `Style`
    // Его неизменяемость позволяет ему шариться между всеми, кто на него ссылается
    // Это агрегация
    style = *Style;
}
Получившаяся иерархия классов
Получившаяся иерархия классов

Продолжение примера, создание объектов:

// Создаем объект класса Scene и сохраняем в переменной
// `root` это композитная ссылка (с единственным владельцем)
root = Scene;

// Создаем объект класса Style; заполняем его, вызывая методы инициализации;
// замораживем, превращая в неизменяемый объект с помощью *-оператора
// `normal` это агрегирующая ссылка
// (другие ссылки могут ссылаться на тот же экземпляр Style)
normal = *Style.font(times).size(40).weight(600); 

// Создаем объект класса Label, инициализируем поля и вставляем в `scene`
root.elements.add(
   Label.at(10, 20).setText("Hello").setStyle(normal));

// Настраиваем ссылку из `scene` в `Label`
root.focused := &root.elements[0];
Иерархия созданных объектов и ссылок между ними
Иерархия созданных объектов и ссылок между ними

Сконструированная нами структура данных дает несколько важных гарантий целостности:

root.elements.add(root.elements[0]);
// ошибка компиляции: объект `Label` может иметь только одного владельца

normal.weight := 800;
// ошибка компиляции: `normal` - неизменяемый объект

root.focused.hide();
// ошибка компиляции: нет проверки и реакции на оборванную ссылку `focused`

Но Аргентум не только следит за программистом (и бьет его по рукам). Он еще и помогает, попробуем исправить ошибки компиляции:

root.elements.add(@root.elements[0]);
// @-оператор глубокого копирования.
// В этой строке на сцену добавляется _копия_ Label,
// которая ссылается на _копию_ текста, но шарит тот же самый Style-объект.

normal := *(@normal).weight(800);
// Сделать изменяемую копию Style-объекта,
// изменить в нем weight,
// заморозить эту копию (сделать ее неизменяемой)
// и пусть `normal` ссылается не нее.

root.focused? _.hide();
// Защитить объект по ассоциативной ссылке от удаления,
// и если он не пуст, вызвать его метод.
// после чего снять с объекта защиту.

Все операции по копированию, заморозке, разморозке, удалению, передаче между потоками и т.д. выполняются автоматически. Компилятор строит эти операции используя composition-aggregation-association-деклараций в полях объектов.

Например, если написать:

newScene := @root;

...то будет сделана полная копия сцены с правильно настроенными внутренними ссылками:

Автоматически созданная копия поддерева сцены с сохранением топологии внутренних ссылок.
Автоматически созданная копия поддерева сцены с сохранением топологии внутренних ссылок.

Обратите внимание:

  • все подобъекты, которые должны иметь единственного владельца копируются каскадно.

  • объекты, помеченные как шаренные (Style) не участвуют в копировании

  • в копии сцены поле focused правильно ссылается на копию label.

Автоматизация ключевых операций над иерархиями объекта в Аргентуме:

  • обеспечивает memory safety

  • обеспечивает отсутствие утечек памяти (и делает ненужным сборщик мусора)

  • гарантирует своевременное удаление объектов, что позволяет автоматически управлять через RAII ресурсами отличными от оперативной памяти: автоматически закрывать файлы, сокеты, хэндлы,

  • гарантирует отсутствие повреждений в логической структуре объектной модели,

  • разгружает программиста от рутинной ручной реализации этих операций.

Промежуточные итоги

Язык Аргентум построен на новой, но уже хорошо знакомой по UML, ссылочной модели, котрая свободна от ограничений и недостатков систем со сборкой мусора, подсчетом ссылок и ручным управлением памятью. На сегодняшний день в языке уже есть: параметризованные классы и интерфейсы; многопоточность; управляющие конструкции, основанные на optional-типах данных; быстрые приведения типов и очень быстрые вызовы методов интерфейсов; модульность и FFI. Он обеспечивает безопастность памяти, типов, отсутствие утечек памяти, гонок и блокировок. Он использует LLVM для генерации кода и строит отдельно стоящие исполняемые приложения.

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

Домашняя страница проекта Аргентум с демкой и туториалами: aglang.org.

В следующей статье будет освещена семантика операций над UML-указателями и их реализация в Aгентуме.

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


  1. Mingun
    23.07.2023 13:41
    +6

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


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


    1. kotan-11 Автор
      23.07.2023 13:41
      +6

      Они разные. В некотором смысле - противоположные.

      Rust позицируется как язык системного программирования, все его сильные и слабые стороны диктуются этим позицированием. Rust не гарантирует отсутствие утечек памяти. Rust фокуссируется на владении и временах жизни стековых и инлайненных объектов (простых и Box-ed), предлагая для сложных структур в хипе указатели Rc, RefCell, которые разрешают шаринг изменяемых объектов циклы и другие архитектурно-опасные решения. Rust не умеет автоматически копировать/клонировать иерархии объектов, соединеных умными указателями, заставляя писать множество опасного кода вручную. Rust любит панику и рантайм проверки.

      Аргентум позицируется как язык прикладного уровня: Он гарантирует отсутствие утечек памяти для любого кода, который успешно скомпилировался. Аргентум управляет временем жизни объектов и в стеке и в хипе, он делает это не с помощью явных деклараций lifetimes, а в соответсвии с принципами агрегации-компизиции-ассоциации, что гораздо ближе к предметной области приложений и привычно по UML. Аргентум сам генерирует сервисный код обслуживающий иерархии объектов. Аргентум не позволяет модифицировать шареные объекты, взламывать систему типов кастами, не имеет unsafe режима, не позволяет обращаться по null-pointer и т.д. Он более ограниченный чем Rust но более безопасный.


      1. Mingun
        23.07.2023 13:41
        +3

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

        Гм. А разве проблема обнаружения утечки не является эквивалентной проблеме останова? Хотя тут нужно определить, что является утечкой. А то, если пихать в хешмапу объекты и ключи забывать, то формально утечки вроде нет, а фактически — есть. И говоря про гарантии утечек Rust скорее всего эту ситуацию и имеет ввиду.


        Rust не умеет автоматически копировать/клонировать иерархии объектов, соединеных умными указателями

        Опять же непонятно.



        Что именно недостает Rust-у для копирования? Зато к вашему примеру есть вопросы. Что будет, если focused будет указывать не на Label своего объекта, а соседнего? По вашему описанию, ссылка любая. Куда после копирования она будет указывать? А если владелец focused будет не Scene, а промежуточный объект внутри сцены и его копируют в ту же сцену?:


        class Scene {
           // Поле `elements` владеет объектом `Array`,
           // который владеет множеством `SceneItem`s
           // Это композиция
           elements = Array(SceneItem);
        
           // Поле `focusedArray` владеет объектом `Array`,
           // который владеет множеством `FocusedHolder`s
           // Это композиция
           focusedArray = Array(FocusedHolder);
        }
        class FocusedHolder {
           // Поле `focused` ссылается на произвольный `SceneItem`. Без владения
           // Это ассоциация
           focused = &SceneItem;
        }

        Копируем scene.focusedArray[0] в scene.focusedArray[1]. Куда укажет ссылка scene.focusedArray[1].focused? Если туда же, куда scene.focusedArray[0].focused, то почему при копировании всей сцены целиком она будет указывать в другое место (судя по вашему примеру)?


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


        1. kotan-11 Автор
          23.07.2023 13:41
          +1

          А то, если пихать в хешмапу объекты и ключи забывать, то формально утечки вроде нет, а фактически — есть. И говоря про гарантии утечек Rust скорее всего эту ситуацию и имеет ввиду.

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

          При таком определении в расте есть утечки, вызванные циклическими ссылками. И архитекторы Раста перекладывают борьбу с этими утечками на плечи программиста. Подробности тут: https://doc.rust-lang.org/book/ch15-06-reference-cycles.html


          1. Mingun
            23.07.2023 13:41

            Забывать — это значит, вы кладете в хешпаму что-то по ключу, но никогда не достаете из нее по этому ключу и не удаляете по этому ключу. А также никогда не обходите все значения, то есть мапа у вас служит кешем — только положить по ключу, достать по ключу и удалить по ключу.


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



        1. kotan-11 Автор
          23.07.2023 13:41

          Опять же непонятно.std::rc::Rc реализует Clone.

          Что именно недостает Rust-у для копирования?

          Реализация std::rc::Rc Clone (как и все прочие из приведенного списка) не копирует иерархию объектов. Она копирует ссылку на существующий объект, нарушая инвариант UML-композиции, который требует наличия единственного вдалельца. Поэтому Rc ссылка непригодна для организации композитов - а ведь на композитах построены все модели данных в современных приложениях - HTML DOM, Compiler AST, XML-JSON, базы данных, офисные документы, файловые системы, сцены GUI и 3D приложений - это всё композиты. И копирование композитов в Расте приходится писать вручную заново для каждой структуры данных.


          1. Mingun
            23.07.2023 13:41
            +1

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


            Глубокое копирование в расте таких композитов имплементируется одной строчкой — #[derive(Clone)] на структуре-владельце.


    1. titovmaxim
      23.07.2023 13:41

      Проблема вообще мало понятная. Когда пишешь, нужно думать. Хоть малечко. Большие проекты. Все на C++. Один раз в жизни посмотрел на память. Ничего интересного не увидел. Обычные sbared, вкупе с UI weak , иногда. Чего сложного?


      1. titovmaxim
        23.07.2023 13:41

        И да. Есть собственная система реактивная, для Qt и Qml, ибо плакать хочется от дефолтной реализации. И все на shared. Без этой всякой ерунды с parent. И без всяких signal/slot , поскольку бесит писать десятки строк в никуда.


  1. nin-jin
    23.07.2023 13:41

    Можно пойти ещё дальше и использовать реактивное программирование для понимания какие объекты ещё нужны, а какие уже не нужны, хотя и доступны по владеющим ссылкам. Во фреймворке $mol это используется для управления жизненным циклом объектов. Там объекты получаются через ленивые владеющие фабрики. Как только этот объект где-то понадобился - он создаётся налету. Как только перестал везде использоваться - уничтожается.


    1. Maccimo
      23.07.2023 13:41
      +12

      Кто о чём, а ниндзя о шмали.


      1. nin-jin
        23.07.2023 13:41

        И по существу, как обычно, ответить нечего.


  1. Tzimie
    23.07.2023 13:41

    Интересно, а как оператор глубокого копирования будет копировать большой связанный клубок объектов с thread safety? Все объекты должны быть заблокированы?


    1. kotan-11 Автор
      23.07.2023 13:41

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

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


  1. Cdracm
    23.07.2023 13:41
    -1

    спасибо, а есть ссылка на язык? гитхаб, мейллист или просто спека?


    1. MonkAlex
      23.07.2023 13:41

      Так в конце ссылка же.

      Сайт https://aglang.org/

      Гитхаб оттуда - https://github.com/karol11/argentum


  1. simenoff
    23.07.2023 13:41

    А GUI из коробки будет?


    1. kotan-11 Автор
      23.07.2023 13:41
      +1

      Будет. Чисто прикладной язык без стандартного гуя никому не нужен.


  1. FreeNickname
    23.07.2023 13:41

    Замечу, что в Swift-е вводят возможность создавать некопируемые структуры / enum-ы, у которых в каждый конкретный момент есть строго один владелец (можно закончить жизненный цикл оператором consume, можно передать в другую функцию временно через borrowing параметр или окончательно через consuming параметр, но в любом случае в каждый момент времени владелец один). Константность и раньше была через let. Кажется, как минимум значительная часть фич Argentum-а этим закрывается?


    1. kotan-11 Автор
      23.07.2023 13:41
      +4

      В аргентуме нет некопируемых структур. Единственность владения обеспечивается не запретом копирования, а удобной встроенной операцией копирования. Let в Swift-e не делает объект константным тогда как оператор заморозки Аргентума - делает.

      Спасибо за фидбэк, он поможет мне написать продолжение про встроенные операции и главное, сделать акцент на отличиях от Раста и Свифта.


  1. agray
    23.07.2023 13:41
    -4

    Если столько проблем с объектами, то не проще ли избавится от объектов вообще? И сразу же с утечками памяти проблем не будет.


  1. freQuensy23
    23.07.2023 13:41
    +5

    Просто оставлю реализацию lifetime-менеджмента дримберда тут


    1. Deosis
      23.07.2023 13:41

      Замечательный язык, который позволяет отстрелить ноги всей команде разработчиков!


  1. rsashka
    23.07.2023 13:41

    root.focused? _.hide();
    // Защитить объект по ассоциативной ссылке от удаления,
    // и если он не пуст, вызвать его метод.
    // после чего снять с объекта защиту.

    А зачем защищать объект и вводить лишние синтаксические конструкции?
    Если компилятор языка знает, что объект - это не владеющая ссылка (ассоциация), то что мешает делать захват объекта и его проверку без участия пользователя, т.е. внутри root.focused.hide(); без всяких root.focused ? _ .hide();


    1. kotan-11 Автор
      23.07.2023 13:41
      +1

      Если делать захват объекта и его проверку без участия пользователя, мы сэкономим два символа в исходном тексте программы, но получим несколько проблем:

      • Из текста будет не ясно, что метод hide может вызваться, а может нет

      • Программисту некуда будет добавить реакцию на оборванную ссылку (else - часть)

      • Если после единственной проверки на оборванность ссылки нужно будет сделать несколько взимосвязанных действий не отпуская объекта, без явного оператора "?" это будет проблематично. А сейчас это делается так:

      focused ? {
         scrollTo(_);
         flash(_);
         _.underline()
      }

      Кстати, "?" это не лишняя конструкция - это бинарный оператор if-then - половинка тернарного оператора if-then-else. Вот про что надо написать! Третий пост будет про управляющие конструкции на основе optional. И про то что в Аргентуме нет bool, а есть optional<void>. Спасибо за коммент.


      1. rsashka
        23.07.2023 13:41

        Из текста будет не ясно, что метод hide может вызваться, а может нет

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

        Программисту некуда будет добавить реакцию на оборванную ссылку (else - часть)

        Классический try ... exept ... else ?

        Если после единственной проверки на оборванность ссылки нужно будет сделать несколько взимосвязанных действий не отпуская объекта, без явного оператора "?" это будет проблематично. А сейчас это делается так:

        Если ? это тернарный оператор, тогда я не понял, какой оператор выполняет захват ссылки.


  1. thevlad
    23.07.2023 13:41
    +3

    Осталось дело за малым, описать собственно каким образом заявленная семантика, различного рода ссылок будет осуществляться? Доступ, по не владеющей ссылки(ассоциации в вашей терминологии) не будет протухать? Когда объект доступный по "разделяемым" ссылкам все таки будет уничтожаться? И прочие не удобные вопросы. А то что управлять деревом из владеющих ссылок просто, это давно известно, вы лишь изобрели unique_ptr (да и Qt есть похожая древовидная модель владения ресурсами/виджетами)

    Просто вы в своих построениях практически в точности воспроизвели, набор "умных" указателей из плюсов (unique_ptr/shared_ptr/weak_ptr), и поэтому хотелось бы понять, зачем все это нужно, тем более чтобы претендовать на статус "нового языка".


  1. Panzerschrek
    23.07.2023 13:41
    +5

    Кажется, у автора не совсем правильные представления о менеджменте памяти в современном C++. Там по сути применяется та же парадигма с наличием одного владельца. Когда надо, можно заиспользовать observer_ptr, а если и его не хватает - shared_ptr/weak_ptr. Никакого "ручного" управления памятью там давно уже нету, никто руками malloc/free new/delete не зовёт, управление памятью происходит через контейнеры стандартной библиотеки, которые это всё внутри по необходимости делают.
    В языке Argentum просто это просто формализовано, хоть это и тоже достижение.

    На счёт разделяемых указателей, встроенных в язык: стоит предусмотреть два вида подобных указателей - однопоточные и многопоточные. В однопоточных внутри просто счётчик ссылок, в многопоточных - атомарный счётчик ссылок плюс какой-нибудь мьютекс или rwlock для доступа к самому объекту. Такое разделение есть, к примеру, в Rust. Смысл в двух разных механизмах - использовать однопоточные типы там, где это возможно, т. к. это не так накладно, как использование многопоточных типов.


  1. Panzerschrek
    23.07.2023 13:41

    Где можно найти компилятор данного языка? Или язык пока только проектируется "на бумаге"?



  1. rsashka
    23.07.2023 13:41

    Если честно, то я так и не понял, зачем специально выделять ссылки на константные объекты (аггрегация)? Ведь они вроде как повторяют поведение обычный (не владеющих) ссылок?


    1. kotan-11 Автор
      23.07.2023 13:41

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


      1. rsashka
        23.07.2023 13:41

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


        1. Akela_wolf
          23.07.2023 13:41
          +2

          Насколько я понял:

          • Невладеющая ссылка никак не влияет на сборку мусора. Объект на который она ссылается может быть удален и тогда невладеющая ссылка окажется "висячей" (аналог WeakReference из Java)

          • Агрегатная ссылка - учитывается сборщиком мусора, объект на который ссылается агрегатная ссылка не будет удален. Но и изменять его нельзя т.к. к нему возможен доступ по агрегатным ссылкам из разных потоков.

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


          1. rsashka
            23.07.2023 13:41

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


            1. Akela_wolf
              23.07.2023 13:41
              +1

              В роли сборщика мусора - компилятор. Это я по джавовской привычке так написал. Насколько я понимаю, там тот же принцип работы что и в C++ с его умными указателями: когда объект ссылки уничтожается (например по выходу из области видимости или по любой другой причине), то его деструктор уменьшает значение счетчика ссылок. Если счетчик ссылок уменьшается до нуля - память освобождается. Собственно, "сборщика мусора" как отдельной подсистемы нет, но эффект освобождения памяти без необходимости заботиться об этом со стороны программиста - аналогичен


              1. rsashka
                23.07.2023 13:41

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

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


                1. Akela_wolf
                  23.07.2023 13:41
                  +1

                  Невладеющие ссылки - могут "провиснуть", агрегатные - не могут. В этом и отличие, насколько я его понял.