Что именно требует компилятор от двух аргументов, связанных одним generic-параметром в этом вызове?
func pair<T>(_ first: T, _ second: T) -> (T, T) {
(first, second)
}
let integer = 1
let decimal = 2.0
let result = pair(integer, integer)
// let error = pair(integer, decimal)
Оба аргумента функции должны иметь один и тот же конкретный тип T. Вызов pair(integer, integer) выводит T как Int, а вызов с Int и Double не компилируется: Swift не выполняет неявное приведение типов ради удовлетворения generic-ограничения.
Обобщённые функции нужны, чтобы один алгоритм работал с разными типами, сохраняя при этом статическую типобезопасность. Такой подход решает проблему дублирования функций для Int, String и других типов без перехода к нетипизированному Any.
Связь нескольких параметров с одним T позволяет описывать не только допустимый набор типов, но и отношения между ними. В данном случае отношение состоит в требовании идентичности типов аргументов.
Если разработчик ожидает автоматического преобразования Int в Double, вызов pair(integer, decimal) приведёт к ошибке компиляции. Generic-параметр T не означает «любой числовой тип» и не запускает динамическое согласование типов.
Это важно для API: повторное использование одного параметра T случайно или намеренно связывает типы аргументов. Если такой связи быть не должно, параметрам следует назначить разные generic-типы или явно выполнить преобразование.
При разборе вызова pair(integer, integer) компилятор видит, что оба значения имеют статический тип Int, и подставляет T == Int. Возвращаемый тип становится (Int, Int).
Вызов с Int и Double не удовлетворяет требованию T: невозможно одновременно подставить в него и Int, и Double. Swift не выбирает общий числовой тип автоматически, потому что это могло бы скрыть потерю точности или другое нежелательное преобразование.
Если нужно разрешить разные типы, их можно описать отдельными параметрами:
Здесь T выводится как Int, а U — как Double. Другой вариант — явно привести значения к одному типу до вызова, например pair(Double(integer), decimal).
Главное ограничение — generic-параметр задаёт статическое условие на этапе компиляции. Это не проверка типов во время выполнения и не механизм числового приведения.
Допустим, функция формирует диапазон, у которого начало и конец должны быть одного типа. Объявление func makeRange<T>(_ lower: T, _ upper: T) защищает API от случайной передачи несовместимых значений.
Можно было бы принять оба аргумента как Any, но тогда проверку типов пришлось бы переносить в тело функции или коду вызывающей стороны, а ошибки проявлялись бы позже. Можно также использовать разные generic-параметры и протоколы, но это усложняет контракт и оправдано только при действительно разных ролях типов.
Выбор одного T предпочтителен, когда значения логически образуют пару одного типа. В результате ошибка обнаруживается при компиляции, а реализация не нуждается в приведениях и runtime-проверках.
T, что значения должны иметь одинаковый динамический тип?Нет, требование относится к типам, с которыми функция скомпилирована. Если T — базовый класс или протокол, оба аргумента могут иметь этот статический тип, хотя фактические экземпляры во время выполнения могут быть разными подклассами.
Нет. Swift не приводит автоматически Int к Double или наоборот для вывода generic-параметра. Приведение нужно записать явно либо изменить сигнатуру функции так, чтобы она принимала подходящий общий протокол или разные типы.
T отличается от двух параметров T и U?Один T связывает типы аргументов: они обязаны совпасть. Параметры T и U независимы, поэтому функция может принять, например, Int и Double, но теряет гарантию одинакового типа; дополнительные отношения между ними пришлось бы задавать отдельными ограничениями.