Как явно выбрать ассоциированный тип нужного трейта, если один тип реализует несколько трейтов с одинаковым именем ассоциированного типа?
Используйте полностью квалифицированное имя: <Тип as Трейт>::АссоциированныйТип. Оно явно связывает обращение с конкретным трейтом и устраняет неоднозначность, когда несколько трейтов определяют ассоциированный тип с одинаковым именем.
Ассоциированные типы позволяют трейту закрепить один связанный тип для каждой реализации, не добавляя этот тип отдельным параметром во все обращения. Однако один тип может реализовать несколько независимых трейтов, и каждый из них может объявлять, например, Item.
Поэтому в языке нужна форма записи, которая указывает не только исходный тип, но и конкретный трейт. Полностью квалифицированный синтаксис решает эту задачу и является частью общего механизма явного выбора элементов трейта.
Если обратиться к T::Item, компилятор может не понять, какой именно ассоциированный тип имеется в виду. Это особенно важно в обобщённом коде, где T ограничен несколькими трейтами с одноимёнными ассоциированными типами.
Неоднозначность нельзя устранить тем, что типы случайно совпадают в текущей реализации. Компилятор обязан проверять корректность на уровне контрактов трейтов, поэтому требуется явное указание источника типа.
Синтаксис <T as Trait>::Item означает: взять ассоциированный тип Item из реализации Trait для типа T. Для конкретного типа аналогично используется запись <Concrete as Trait>::Item.
Минимальный пример:
Здесь Number равен u32, а Text — String. Простая запись Value::Item не выражает, какой из двух трейтов нужно выбрать.
В обобщённой функции применяется тот же синтаксис, например <T as First>::Item. Если функция использует методы или свойства обоих трейтов, явная квалификация делает контракт читаемым и предотвращает зависимость от неявного разрешения имён.
Ограничение этого подхода в том, что выбранный трейт должен быть доступен как bound или следовать из других ограничений. Если для T не доказана реализация нужного трейта, обращение к его ассоциированному типу не скомпилируется.
В библиотеке тип адаптера реализует два трейта протокола, и оба задают ассоциированный тип Error. Внутренний код должен выбрать ошибку транспортного уровня, а не ошибку формата данных.
Первый вариант — переименовать один из ассоциированных типов. Это делает API проще для пользователей, но невозможно, если трейты внешние или уже опубликованы. Второй вариант — создать промежуточные типы-обёртки; он изолирует неоднозначность, но добавляет лишние типы и преобразования.
Выбран полностью квалифицированный синтаксис <Adapter as Transport>::Error. Он не меняет публичный API, точно фиксирует семантику и сохраняет возможность использовать второй Error в другом месте. В результате код компилируется независимо от того, совпадают ли конкретные типы ошибок в текущей реализации.
Нет, совпадение конкретных типов не устраняет неоднозначность самого обращения. Компилятор разрешает имя по структуре трейтов, а не по сравнению результатов реализации. Нужно написать <T as First>::Item или <T as Second>::Item.
Для метода можно использовать форму Trait::method(&value) или более явную <Type as Trait>::method(&value). Для ассоциированного типа применяется форма <Type as Trait>::Item, потому что тип является частью пути типов, а не вызываемой операцией. В обоих случаях принцип один: явно указать трейт-источник.
Само наличие обоих bound не является ошибкой. Ошибка возникнет только при неоднозначном обращении без квалификации. Запись <T as First>::Item выберет первый контракт, а <T as Second>::Item — второй; каждый выбранный тип можно использовать независимо в сигнатуре или теле функции.