В публичной структуре все хранимые свойства объявлены публичными, но внешний модуль не может вызвать автоматически созданный поименованный инициализатор. Какое правило Swift объясняет это поведение?
Автоматически созданный поименованный инициализатор структуры не становится public только потому, что сама структура и её свойства публичны. Для создания экземпляров из другого модуля нужно явно объявить собственный public init.
Swift поддерживает автоматическую генерацию поименованных инициализаторов структур, чтобы не заставлять разработчика писать шаблонный код для простых моделей. Одновременно система уровней доступа должна предотвращать случайное расширение публичного API модуля.
Поэтому синтезированный инициализатор остаётся внутренней деталью реализации типа, если разработчик явно не сделал его частью внешнего контракта.
Публичная структура может выглядеть полностью готовой для использования клиентским кодом: её имя и свойства доступны извне. Однако вызов автоматически созданного инициализатора из другого модуля завершится ошибкой доступа, потому что такой инициализатор обычно имеет уровень internal, а не public.
Если рассчитывать на синтезированный инициализатор как на часть API, изменение объявления типа или перенос кода в отдельный модуль может неожиданно сломать сборку клиента. Это особенно важно для библиотек и SDK.
Уровень доступа структуры и уровень доступа её инициализатора — независимые свойства. Для публичного API нужно явно предоставить инициализатор:
Теперь внешний модуль может вызвать Endpoint(host:port:). Инициализатор явно устанавливает все хранимые свойства, поэтому структура сохраняет свои инварианты и одновременно получает требуемый уровень доступа.
Автоматически созданный memberwise-инициализатор удобен внутри модуля: он принимает параметры для хранимых свойств и учитывает их значения по умолчанию. Но для публичной структуры его доступность не повышается автоматически до public.
Явный инициализатор также позволяет не раскрывать внутреннее представление типа. Например, можно заменить хранимое свойство, добавить проверку диапазона порта или изменить порядок параметров, не меняя внешний контракт. Компромисс состоит в том, что разработчик становится ответственным за инициализацию всех свойств и поддержку совместимости API.
Библиотека публикует структуру конфигурации с несколькими публичными свойствами. Сначала разработчики используют синтезированный инициализатор внутри самого пакета, но после подключения библиотеки клиентское приложение не может создать конфигурацию.
Вариант оставить всё как есть сохраняет минимум кода, но не предоставляет внешний API. Вариант сделать свойства публичными без явного инициализатора не решает проблему: доступность свойств не меняет уровень доступа инициализатора.
Правильное решение — объявить public init, проверить входные данные и при необходимости скрыть часть состояния. В результате клиент получает стабильный способ создания значения, а библиотека сохраняет контроль над инвариантами и будущими изменениями реализации.
Достаточно ли сделать саму структуру public, чтобы её memberwise-инициализатор стал публичным?
Нет. Уровень доступа типа не повышает автоматически уровень доступа синтезированного инициализатора. Для вызова из другого модуля необходим явно объявленный public init.
Почему нельзя считать публичные свойства заменой публичному инициализатору?
Свойства определяют возможность читать или изменять состояние уже созданного экземпляра, а инициализатор определяет способ создания экземпляра. Это разные элементы API с независимыми уровнями доступа. Кроме того, явный инициализатор может проверять входные данные и гарантировать инварианты, которые нельзя обеспечить одной публичностью свойств.
Что произойдёт с внешним кодом, если позже добавить собственный инициализатор?
Наличие собственного инициализатора меняет набор автоматически синтезируемых инициализаторов: ожидаемый memberwise-вариант может перестать предоставляться в прежнем виде. Поэтому библиотечный тип не должен неявно полагаться на сгенерированный инициализатор как на стабильную часть публичного API; его следует объявить явно и документировать.