Контракты и полиморфизм
Контракт — описание требуемых методов, аналог интерфейса:
контракт Печатаемый
функция напечатать(): текст;
конец
структура Кот : Печатаемый
функция напечатать(): текст
вернуть "Мяу";
конец
конец
структура Собака : Печатаемый
функция напечатать(): текст
вернуть "Гав";
конец
конец
Компилятор на этапе сборки проверяет, что структура, заявляющая `: Контракт`, реально имеет все требуемые методы с совпадающими сигнатурами.
Настоящий полиморфизм
Контракт можно использовать как тип параметра функции или переменной — один и тот же код тогда работает с любой структурой, реализующей контракт (динамический выбор нужной реализации в момент выполнения):
функция озвучить(Печатаемый существо): целое
вывести(существо.напечатать());
вернуть 0;
конец
функция главная(): целое
Кот кот = Кот();
Собака пёс = Собака();
озвучить(кот); // выведет "Мяу"
озвучить(пёс); // выведет "Гав" — та же функция, другая реализация
вернуть 0;
конец
⚠️ Важно: аргумент-структуру нужно сначала сохранить в переменную —
озвучить(Кот()) (временный экземпляр напрямую, без промежуточной
переменной) не работает.
Список\<Контракт\>
Коллекция разнотипных объектов через общий интерфейс — та же идея, что выше, но много объектов сразу:
Список из Печатаемый зоопарк = Список из Печатаемый();
зоопарк.добавить(кот);
зоопарк.добавить(пёс);
// обход «для каждого» тоже работает (пример: examples/контракты_для_каждого.код):
для каждого существо в зоопарк
вывести(существо.напечатать());
конец
целое индекс = 0;
пока (индекс меньше зоопарк.размер())
Печатаемый существо = зоопарк.получить(индекс);
вывести(существо.напечатать()); // "Мяу", затем "Гав"
индекс = индекс + 1;
конец
✅ **Исправлено (раньше было настоящим use-after-free, не просто
утечкой)**: структуры, добавленные в Список из Контракт через
добавить(...), и прочитанные через получить(...), теперь корректно
захватываются (ARC-retain) — счётчик ссылок объекта учитывает и
исходную переменную, и копию внутри списка. Раньше, если исходная
переменная (Кот кот = Кот();) выходила из области видимости раньше
списка (например, внутри функции, добавляющей объект в список,
переданный аргументом), объект освобождался, а список продолжал
хранить указатель на уже освобождённую память — реальный крах/порча
данных при следующем обращении, воспроизведённый и подтверждённый
через valgrind ("Invalid read... freed"), не просто "утечка", как это
раньше (неточно) утверждала документация. ⚠️ Теперь, раз объект
корректно захватывается, у этого честно появляется ОБРАТНАЯ сторона —
настоящая утечка (не крах): Список из Контракт пока не освобождает
захваченные им ссылки — ни через удалить(), ни через .освободить()
(в отличие от обычного Список из Структура, см. выше) — держать
исходные переменные (кот, пёс) живыми столько же, сколько и сам
список, по-прежнему хорошая практика, но теперь как способ не терять
память, а не как способ избежать краха.
Контракт как тип возврата функции
Функция может быть объявлена возвращающей контракт — вызывающий код получает объект, не зная заранее, какая именно структура за ним стоит:
функция создать_питомца(): Печатаемый
Кот кот = Кот();
вернуть кот;
конец
функция главная(): целое
Печатаемый питомец = создать_питомца();
вывести(питомец.напечатать()); // "Мяу"
вернуть 0;
конец
✅ Этот случай, в отличие от Список из Контракт выше, проверен и
оказался УЖЕ безопасным без дополнительных исправлений: вернуть кот;
не освобождает кот при выходе из функции (та же "переезжающая"
семантика владения, что и у обычного возврата структуры без контракта)
— владение просто переходит вызывающей стороне, без двойного счётчика
и без use-after-free. Подтверждено тестом с тремя последовательными
вызовами создать_питомца(...) — вывод корректен, valgrind не находит
ни одной ошибки чтения/записи (объекты-контракты, впрочем, тоже пока
не освобождаются при выходе локальной переменной из области видимости
— та же самая, уже известная категория утечки, что и у Список из T без
.освободить()).