Что происходит при вызове обобщённого метода трейта, если его параметр типа не встречается в аргументах?
Rust пытается вывести такой параметр типа из ожидаемого типа результата и других ограничений контекста. Если этих данных недостаточно, вызов считается неоднозначным и нужно явно указать тип параметра.
Сам bound обычно не выбирает единственный тип: ограничению вроде T: Default могут соответствовать многие типы. Явное указание типа устраняет неоднозначность и фиксирует конкретную мономорфизированную версию метода.
Обобщения в Rust предназначены для повторного использования логики без отказа от статической проверки типов и эффективного машинного кода. Поэтому язык сочетает вывод типов с возможностью явно задавать параметры там, где автоматический вывод не может однозначно определить намерение разработчика.
Такой компромисс позволяет не писать типы в очевидных местах, но не скрывает неоднозначные решения за неявными правилами. Для generic-кода это особенно важно: разные значения параметра типа могут означать разные реализации, разные требования к bound и разные версии скомпилированного кода.
Рассмотрим метод, параметр типа которого не присутствует среди его аргументов. По самим аргументам Rust не может получить информацию о типе результата, поэтому ему приходится использовать ожидаемый тип выражения или явное указание параметра.
Если ни один источник информации не определяет тип однозначно, компилятор не выбирает произвольную реализацию. Иначе вызов мог бы зависеть от случайного решения алгоритма вывода, а bound мог бы соответствовать множеству типов.
Неверное ожидание особенно часто возникает, когда разработчик считает, что ограничение само по себе выбирает тип. Например, T: Default лишь гарантирует наличие конструктора по умолчанию, но не говорит, должен ли T быть строкой, числом или другим типом.
При выводе параметра типа Rust собирает ограничения из нескольких источников:
Если параметр типа отсутствует в аргументах, фактические аргументы не помогают. Тогда выражение вроде присваивания результата переменной с известным типом может дать необходимую информацию, а отсутствие такой аннотации приведёт к ошибке неоднозначности.
Минимальный пример:
В первом вызове тип T выводится как String из типа переменной. Во втором он задан явно синтаксисом turbofish, поэтому контекст вывода не требуется. Вызов без аннотации и без turbofish, например присваивание результата переменной с неопределённым типом, не скомпилируется с ошибкой вида E0282.
Важно различать вывод типа и выбор реализации трейта. После того как T определён, Rust проверяет, выполнены ли все его bounds, и выбирает подходящую реализацию. Для generic-кода это обычно приводит к статической диспетчеризации и мономорфизации отдельной версии для каждого использованного конкретного типа.
Если типов, удовлетворяющих bounds, несколько, Rust не перебирает их до получения «подходящего» результата. Тип должен быть выведен из ограничений контекста или указан разработчиком. Это делает поведение предсказуемым, но иногда требует дополнительной аннотации.
В библиотеке есть универсальный источник значений, а вызывающий код хочет получить из него конкретный тип. Рассматривались два варианта: заставить API принимать тип как обычный аргумент либо оставить тип только параметром результата.
Первый вариант упрощает вывод типа, но может ухудшить API: появляется фиктивный или реально не нужный аргумент, предназначенный только для выбора типа. Второй вариант делает вызов компактнее, но требует ожидаемого типа или явного turbofish; без них вызывающий получает ошибку неоднозначности.
Для метода преобразования был выбран второй вариант, поскольку тип результата уже естественно известен из контекста присваивания. В местах, где контекста нет, используется явное указание параметра. Это сохраняет чистый интерфейс и одновременно делает неоднозначные вызовы явными.
T: Default, чтобы Rust выбрал тип T?Нет. Этот bound задаёт только требование к возможному типу: у него должна быть реализация Default. Ему могут соответствовать многие типы, поэтому bound проверяется после вывода типа, а не заменяет сам вывод.
Если тип не следует из аргументов или ожидаемого результата, нужно добавить аннотацию либо явно указать параметр. Иначе компилятор выдаст ошибку о невозможности определить тип.
Да. Если результат generic-вызова используется там, где ожидается конкретный тип, этот тип может определить параметр метода.
Например, присваивание результата переменной типа String позволяет вывести T = String. Но если результат передаётся в контекст, который сам не имеет конкретного типа, такой информации недостаточно.
Оба приёма могут устранить неоднозначность, но действуют на разных уровнях. Аннотация результата задаёт ожидаемый тип выражения и позволяет компилятору вывести параметр, тогда как turbofish непосредственно фиксирует конкретный generic-параметр вызова.
Явное указание полезно, когда ожидаемый тип трудно выразить или он меняется после дальнейшего рефакторинга. Аннотация результата часто читается естественнее, если тип уже является частью контракта переменной или возвращаемого значения.