Разберите последствие требования равенства двух generic-параметров: какие гарантии получает тело функции сверх их общего соответствия протоколу?
Требование равенства двух generic-параметров означает, что они обозначают один и тот же конкретный тип, а не просто два типа с общими возможностями. Поэтому тело функции может передавать значение одного параметра туда, где ожидается другой, использовать их общие associated types и возвращать результат с однозначно известным типом.
Без такого ограничения Swift рассматривает параметры как независимые типы. Даже если оба соответствуют одному протоколу, это не доказывает их взаимозаменяемость.
Generic constraints появились как средство описывать отношения между типами на этапе компиляции, а не проверять их динамически во время выполнения. Ограничения соответствия протоколам задают доступные операции, а same-type constraints дополнительно фиксируют связи между параметрами.
Такой подход позволяет сохранить статическую типобезопасность и специализировать generic-код без приведения типов или ручных проверок во время выполнения.
Предположим, функция принимает два значения, каждое из которых соответствует одному протоколу. Из этого следует только наличие одинакового набора требований; фактические типы могут быть разными.
Если функция должна объединить значения через операцию, принимающую два экземпляра одного типа, отсутствие требования равенства приведёт к ошибке компиляции. Попытка решить проблему приведением типов ухудшает дизайн: она переносит проверку на выполнение и может привести к отказу в рантайме.
Ограничение равенства связывает generic-параметры на уровне системы типов. Если объявлены параметры A и B и установлено A == B, компилятор рассматривает их как один тип во всех местах данного generic-контекста.
Например, протокол может описывать операцию над двумя значениями собственного типа:
Вызов A.plus(a, b) корректен, потому что условие A == B доказывает: значение b имеет тот же тип, что и a. Одного ограничения A: Summable, B: Summable было бы недостаточно: соответствие одному протоколу не делает два независимых типа одинаковыми.
Равенство также может связывать associated types. Например, ограничения могут гарантировать, что результат одной операции имеет тот же тип, что и вход другой операции. Это даёт generic-коду возможность передавать значения между API без дополнительных where-условий и преобразований.
Важное ограничение: same-type constraint не означает равенство только представлений или совместимость по протоколу. Он требует идентичности конкретного типа в данном контексте. Поэтому функция с таким условием принимает только вызовы, для которых компилятор может доказать равенство статически.
В библиотеке обработки числовых значений сначала можно объявить функцию с двумя независимыми параметрами, соответствующими Summable. Такой вариант гибче по сигнатуре, но не позволяет безопасно вызвать операцию, требующую два значения одного типа.
Другой вариант — принять оба значения как existential-протоколы. Это позволяет хранить разные реализации, но стирает конкретный тип и не доказывает, что оба значения имеют одинаковую реализацию; производительность и доступные операции также могут измениться.
Выбранный вариант с равенством generic-параметров сохраняет статическую проверку и не требует type erasure. В результате функция принимает только корректные пары значений, а ошибка несовместимого вызова обнаруживается на этапе компиляции.
Дополнительный вопрос 1: Достаточно ли того, что оба параметра соответствуют одному протоколу?
Нет. Соответствие протоколу описывает набор доступных требований каждого типа, но не устанавливает равенство самих типов. Два разных типа могут реализовать один протокол независимо друг от друга, поэтому для передачи значения одного параметра вместо другого нужно отдельное ограничение равенства.
Дополнительный вопрос 2: Чем равенство generic-параметров отличается от приведения к протоколу?
Приведение к протоколу говорит, что значение поддерживает требования протокола, но не связывает его с другим значением или параметром как с тем же конкретным типом. Ограничение равенства сохраняет конкретную типовую связь статически, поэтому компилятор может проверить вызовы и выбрать операции без динамического приведения.
Дополнительный вопрос 3: Может ли вызов функции с таким ограничением быть отклонён, даже если фактические значения совместимы?
Да. Совместимость по содержимому, наследованию или общему протоколу не заменяет доказательство идентичности типов. Вызов допустим только тогда, когда вывод типов и известные ограничения позволяют компилятору установить, что оба generic-параметра обозначают один и тот же тип.