Оцените последствие добавления нового associated type в публичный протокол: что произойдёт с уже существующими соответствиями типов?
Добавление нового associated type обычно делает публичный протокол обратно несовместимым на уровне исходного кода: существующие типы могут перестать соответствовать протоколу, если Swift не способен вывести новый тип и для него нет значения по умолчанию. Все реализации протокола и generic-код, зависящий от этого соответствия, придётся проверить и при необходимости обновить.
Associated types позволяют протоколу описывать тип, связанный с конкретным соответствующим типом: например, элемент последовательности или результат преобразования. Это решает проблему описания отношений между типами без привязки протокола к одному заранее известному конкретному типу.
Однако требования протокола являются контрактом для всех conformances. Изменение такого контракта после публикации библиотеки затрагивает не только сам протокол, но и сторонние типы, которые реализуют его в другом модуле.
Рассмотрим библиотеку, в которой уже опубликован протокол и несколько типов ему соответствуют. Если в протокол добавить новый associated type, каждый conforming type должен предоставить конкретный тип для этого требования — явно или через вывод из других требований.
Если тип вывести нельзя, соответствие перестанет компилироваться. Это особенно опасно для протоколов, которые реализуются клиентским кодом или через retroactive conformance в независимых модулях: владелец библиотеки не контролирует все затронутые реализации.
Swift проверяет соответствие протоколу по всем его требованиям. Для нового associated type компилятор ищет явный typealias, вывод из сигнатур свойств, методов или других связанных требований, либо объявленный в протоколе тип по умолчанию.
Здесь Item выводится как String из параметра save. Если после публикации добавить, например, associatedtype Identifier, соответствие сохранится только при наличии способа вывести или задать Identifier; иначе FileStore перестанет соответствовать Store.
Значение по умолчанию может смягчить изменение:
Но это не делает изменение полностью безопасным. Соответствующий тип может явно выбрать другой Identifier, а новый associated type всё равно меняет семантику протокола, его generic-ограничения и потенциально ABI-контракт публичной библиотеки. Кроме того, если новое требование должно быть обязательным и не имеет разумного общего значения, добавление default-типа лишь скрывает проблему проектирования.
Безопаснее заранее закладывать расширяемость протокола, использовать отдельный дочерний протокол для новой возможности или выпускать новую версию протокола. Дочерний протокол не ломает старые соответствия, но требует от клиентов явно перейти на новый контракт.
Команда поддерживает публичный протокол Repository, которому соответствуют типы из приложения и нескольких SDK. Появилась потребность в идентификаторе записи, поэтому рассматриваются два варианта.
Первый — добавить associatedtype ID прямо в Repository. Плюс: все новые реализации сразу получают единый контракт. Минус: существующие и внешние conformances могут сломаться, а generic-функции с ограничением Repository потребуют повторной проверки.
Второй — создать IdentifiableRepository, наследующий Repository и добавляющий associatedtype ID. Старые реализации продолжают работать, а новые компоненты могут требовать уже расширенный протокол. Этот вариант выбран, потому что сохраняет совместимость и явно разделяет старый и новый уровни возможностей.
Нет. Если тип может вывести новый associated type из уже существующих требований или протокол задаёт подходящее значение по умолчанию, исходный код может продолжить компилироваться. Но проверка совместимости всё равно обязательна: вывод может быть неоднозначным, а default-тип может изменить ожидаемую семантику generic-кода.
Метод с реализацией по умолчанию часто позволяет старым типам сохранить соответствие без изменений. Associated type — это не поведение, а новый типовой параметр контракта; он должен быть определён или выведен. Поэтому default implementation метода обычно проще использовать для совместимости, чем добавление нового типа, хотя оба изменения могут влиять на API и семантику.
Старое соответствие родительскому протоколу не обязано автоматически соответствовать дочернему. Поэтому добавление требований в дочерний протокол не инвалидирует существующие conformances родительского. Компромисс в том, что generic-код, которому нужен новый associated type, должен явно принимать дочерний протокол, а старые реализации нельзя использовать там без дополнительного соответствия.