В библиотечном трейте параметр типа имеет значение по умолчанию: что именно происходит при использовании трейта без явного параметра?
Параметр типа по умолчанию автоматически подставляется, когда конкретный аргумент этого параметра не указан. В трейте Add<Rhs = Self> запись Add означает Add<Self>, но это не создаёт реализацию автоматически и не запрещает явно выбрать другой тип Rhs.
Такой механизм появился для обобщённых трейтов, где один параметр обычно совпадает с основным типом. Классический пример — операции: для сложения чаще всего правый операнд имеет тот же тип, поэтому Rhs = Self избавляет от повторения.
При этом библиотека сохраняет возможность поддержать дополнительные варианты, например сложение значения с числом другого типа. Таким образом, default-параметр сочетает удобство типичного случая с расширяемостью API.
Без понимания этого правила разработчик может ошибочно считать Add отдельным трейтом или предположить, что значение по умолчанию выбирается только после неудачного вывода типов. На самом деле оно подставляется сразу при отсутствии явного аргумента.
Это важно для обобщённых bound: ограничение T: Add проверяет реализацию Add<T>, если у Add правый операнд по умолчанию равен Self. Для другого типа требуется явно написать соответствующий параметр.
Пусть трейт концептуально объявлен как Add<Rhs = Self>. Тогда:
Add интерпретируется как Add<Self>;Add<U> явно требует операнд типа U;impl.Минимальный пример:
Первая реализация фактически является реализацией Add<Score> for Score, потому что Rhs не указан и берётся Self. Вторая реализация явно задаёт Rhs = i32; обе допустимы, поскольку это разные специализации трейта для разных правых операндов.
Default-параметр действует только там, где аргумент не указан. В bound T: Add он означает T: Add<T>, а bound T: Add<u32> требует именно возможность сложить T с u32. Значение по умолчанию не является механизмом специализации: Rust не выбирает «более подходящую» реализацию среди пересекающихся impl и не разрешает перекрывающиеся реализации.
Практическое ограничение связано с coherence. Нельзя объявить две реализации, которые после подстановки параметров описывают один и тот же случай. Кроме того, изменение default-параметра в публичном API может изменить смысл неполных bound и поведение кода, поэтому такое изменение требует осторожности.
Команда проектирует тип Money и хочет поддержать сложение двух денежных значений. Позже появляется требование складывать Money с целым числом единиц.
Вариант без default-параметра заставил бы пользователей постоянно писать тип правого операнда даже для обычного случая. Это повышает шум в bound и реализациях, хотя типичный сценарий очевиден.
Вариант с default-параметром позволяет определить обычное сложение как Add<Money> через краткую форму Add, а дополнительный случай выразить отдельной реализацией Add<i32>. Это выбранное решение: оно сохраняет удобный синтаксис и одновременно не ограничивает набор поддерживаемых правых операндов.
Использовать вместо этого associated type для правого операнда было бы менее гибко: у типа обычно была бы одна фиксированная разновидность правого операнда. Параметр трейта позволяет иметь несколько различных реализаций, если они не нарушают правила согласованности.
T: Add то же самое, что отсутствие ограничений на правый операнд?Нет. Если объявление трейта содержит Rhs = Self, то T: Add означает T: Add<T>. Это ограничение на конкретную специализацию, а не разрешение на любой правый операнд. Чтобы потребовать сложение с u32, нужно выразить T: Add<u32> и, при необходимости, отдельно ограничить Output.
Нет. Default-параметр только сокращает запись аргумента типа. Для Add всё равно должна существовать подходящая реализация, включая связанный с ней Output и метод операции. Если impl Add for MyType не объявлен, одна лишь запись Add не добавит поведение автоматически.
Нет. Например, default Rhs = Self не даёт возможности без конфликтов объявить blanket-реализацию для всех Rhs, а затем отдельную реализацию для Rhs = Self. После подстановки эти случаи могут пересекаться, а стабильный Rust не поддерживает произвольную специализацию таких impl. Default-параметры упрощают выбор аргумента типа, но не меняют правила coherence и разрешения пересечений.