Контракты и полиморфизм

Контракт — описание требуемых методов, аналог интерфейса:

контракт Печатаемый
    функция напечатать(): текст;
конец

структура Кот : Печатаемый
    функция напечатать(): текст
        вернуть "Мяу";
    конец
конец

структура Собака : Печатаемый
    функция напечатать(): текст
        вернуть "Гав";
    конец
конец

Компилятор на этапе сборки проверяет, что структура, заявляющая `: Контракт`, реально имеет все требуемые методы с совпадающими сигнатурами.

Настоящий полиморфизм

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

функция озвучить(Печатаемый существо): целое
    вывести(существо.напечатать());
    вернуть 0;
конец

функция главная(): целое
    Кот кот = Кот();
    Собака пёс = Собака();

    озвучить(кот);   // выведет "Мяу"
    озвучить(пёс);   // выведет "Гав" — та же функция, другая реализация

    вернуть 0;
конец

⚠️ Важно: аргумент-структуру нужно сначала сохранить в переменную — озвучить(Кот()) (временный экземпляр напрямую, без промежуточной переменной) не работает.

Список\<Контракт\>

Коллекция разнотипных объектов через общий интерфейс — та же идея, что выше, но много объектов сразу:

Список из Печатаемый зоопарк = Список из Печатаемый();
зоопарк.добавить(кот);
зоопарк.добавить(пёс);

// обход «для каждого» тоже работает (пример: examples/контракты_для_каждого.код):
для каждого существо в зоопарк
    вывести(существо.напечатать());
конец

целое индекс = 0;
пока (индекс меньше зоопарк.размер())
    Печатаемый существо = зоопарк.получить(индекс);
    вывести(существо.напечатать());   // "Мяу", затем "Гав"
    индекс = индекс + 1;
конец

✅ **Исправлено (раньше было настоящим use-after-free, не просто утечкой)**: структуры, добавленные в Список из Контракт через добавить(...), и прочитанные через получить(...), теперь корректно захватываются (ARC-retain) — счётчик ссылок объекта учитывает и исходную переменную, и копию внутри списка. Раньше, если исходная переменная (Кот кот = Кот();) выходила из области видимости раньше списка (например, внутри функции, добавляющей объект в список, переданный аргументом), объект освобождался, а список продолжал хранить указатель на уже освобождённую память — реальный крах/порча данных при следующем обращении, воспроизведённый и подтверждённый через valgrind ("Invalid read... freed"), не просто "утечка", как это раньше (неточно) утверждала документация. ⚠️ Теперь, раз объект корректно захватывается, у этого честно появляется ОБРАТНАЯ сторона — настоящая утечка (не крах): Список из Контракт пока не освобождает захваченные им ссылки — ни через удалить(), ни через .освободить() (в отличие от обычного Список из Структура, см. выше) — держать исходные переменные (кот, пёс) живыми столько же, сколько и сам список, по-прежнему хорошая практика, но теперь как способ не терять память, а не как способ избежать краха.

Контракт как тип возврата функции

Функция может быть объявлена возвращающей контракт — вызывающий код получает объект, не зная заранее, какая именно структура за ним стоит:

функция создать_питомца(): Печатаемый
    Кот кот = Кот();
    вернуть кот;
конец

функция главная(): целое
    Печатаемый питомец = создать_питомца();
    вывести(питомец.напечатать());   // "Мяу"
    вернуть 0;
конец

✅ Этот случай, в отличие от Список из Контракт выше, проверен и оказался УЖЕ безопасным без дополнительных исправлений: вернуть кот; не освобождает кот при выходе из функции (та же "переезжающая" семантика владения, что и у обычного возврата структуры без контракта) — владение просто переходит вызывающей стороне, без двойного счётчика и без use-after-free. Подтверждено тестом с тремя последовательными вызовами создать_питомца(...) — вывод корректен, valgrind не находит ни одной ошибки чтения/записи (объекты-контракты, впрочем, тоже пока не освобождаются при выходе локальной переменной из области видимости — та же самая, уже известная категория утечки, что и у Список из T без .освободить()).