При вызове generic-функции, у которой параметр типа фигурирует только в ограничении протокола, почему Swift не может вывести этот параметр?
Swift использует ограничения протокола, чтобы проверить уже выбранный тип, но не выбирает конкретный тип только на основании самого факта соответствия. Если generic-параметр не встречается в аргументах или контексте результата, у компилятора нет единственного кандидата для вывода.
Нужно передать тип явно через аргумент, использовать контекст, позволяющий однозначно вывести результат, либо изменить API так, чтобы тип участвовал во входных данных.
Generic-код в Swift строится вокруг статической проверки типов. Ограничение вроде соответствия протоколу описывает множество допустимых типов, но обычно не сужает его до одного конкретного типа.
Такой подход предотвращает произвольный выбор типа компилятором. Иначе одна и та же функция могла бы получить разные корректные специализации в зависимости от неявного решения компилятора.
Рассмотрим функцию, регистрирующую любой сервис:
Вызов не компилируется: T нельзя вывести. Протокол Service допускает Email и любое другое соответствующее ему объявление, поэтому ограничение не определяет конкретный тип.
Неверно считать, что компилятор должен автоматически выбрать единственный известный в текущем файле тип. В проекте могут существовать другие соответствия, а generic-функция не обязана знать о конкретной реализации.
При выводе generic-типов Swift анализирует фактические аргументы и доступный контекст. Ограничения T: Service, T: Equatable или аналогичные используются после появления кандидата, чтобы проверить допустимость операций и соответствие требованиям.
Если тип входит в аргумент, вывод однозначен:
Здесь T выводится как Email, а ограничение T: Service подтверждает корректность вызова. Само ограничение не является фабрикой типа и не выбирает реализацию автоматически.
Контекст результата иногда также может участвовать в выводе типа, но он должен однозначно задавать T. Если нескольким типам соответствует один ожидаемый результат или generic-параметр вообще не связан с результатом, этого недостаточно.
Практическое правило: каждый generic-параметр должен быть связан с аргументом, возвращаемым значением или явно заданным типовым контекстом. Если параметр используется только в ограничении, API обычно нуждается в явном параметре типа или в другой форме проектирования.
Допустим, инфраструктура регистрирует реализации Service. Можно оставить пустой generic-вызов и требовать от вызывающего кода неявного выбора типа, но это не компилируется и не объясняет, какую реализацию нужно зарегистрировать.
Первый вариант — передавать метатип, например Email.self. Он прост, однозначен и сохраняет статическую проверку соответствия протоколу, но немного увеличивает синтаксис вызова.
Второй вариант — создать негeneric-обёртки вроде registerEmail(). Это делает API выразительным, однако приводит к дополнительному коду и плохо масштабируется при большом числе реализаций.
Обычно выбирают передачу метатипа или самого экземпляра: тип становится частью входных данных, Swift выводит его без догадок, а generic-ограничение продолжает проверять корректность. В результате исчезает неоднозначность и сохраняется переиспользуемость API.
Нет. Вывод типов не основан на поиске всех объявлений в модуле. Компилятор выводит тип из локального выражения и его контекста, а не выбирает случайный или единственный на данный момент conforming-тип.
Нельзя. Ограничение задаёт множество допустимых типов, но не значение по умолчанию. Чтобы задать конкретный тип, его нужно связать с аргументом, результатом, контекстом или предоставить отдельный негeneric-вход.
Если ожидаемый тип результата однозначно связывает его с T, вывод может быть успешным. Но при отсутствии контекста или при наличии нескольких возможных связей параметр останется неизвестным. Поэтому API, создающее значение неизвестного типа без входного типа, часто требует явного контекста или отдельного конструктора для конкретной реализации.