Почему метод трейта с возвращаемым impl Trait нельзя вызвать через dyn Trait?
Метод трейта с возвращаемым impl Trait делает трейт несовместимым с trait object, поэтому вызов через dyn Trait невозможен. Причина в том, что impl Trait скрывает конкретный тип, а таблица виртуальных методов должна иметь заранее определённый ABI и однозначный тип результата для каждого метода.
Такой метод можно использовать при статической диспетчеризации через обобщённый параметр или заменить на возвращаемый Box<dyn Trait> для динамической диспетчеризации.
Generics в Rust предназначены для статической диспетчеризации: компилятор знает конкретный тип и может мономорфизировать функцию. Напротив, dyn Trait создавался для работы с разными реализациями через единый интерфейс во время выполнения.
impl Trait в возвращаемой позиции позволяет скрыть конкретный тип, сохранив его единственность для данной функции или реализации. Этот механизм удобен для абстрагирования сложных типов итераторов, но он не предоставляет динамический тип результата, необходимый trait object.
Рассмотрим трейт, который должен хранить разные реализации и вызывать их через единый объект. Если его метод возвращает impl Trait, вызывающий код знает только, что результат реализует некоторый трейт, но не знает конкретный размер и структуру скрытого типа.
Для виртуального вызова это критично: вызов через vtable должен заранее понимать, как передать результат, кто отвечает за его размещение и какой машинный ABI использовать. Если требуется гетерогенное хранение реализаций, попытка применить dyn Trait приведёт к ошибке несовместимости трейта с trait object.
impl Trait в возвращаемом типе означает: функция возвращает один конкретный скрытый тип, выбранный компилятором. Это не означает, что она возвращает произвольный тип, реализующий указанный трейт, при каждом вызове.
Для generic-кода это работает, потому что компилятор подставляет конкретный тип и генерирует специализированный вариант функции:
Метод make здесь пригоден для статической диспетчеризации через F: Factory. Но dyn Factory использовать нельзя: возвращаемый opaque-тип не имеет единого описания, которое можно было бы встроить в vtable.
Для динамической диспетчеризации обычно возвращают объект с известным указателем и vtable:
Компромисс очевиден: impl Trait обычно позволяет избежать выделения в куче и сохранить статическую оптимизацию, а Box<dyn Trait> даёт динамическую совместимость ценой косвенного вызова и, как правило, heap allocation.
Альтернативой может быть ассоциированный тип, если конкретный тип результата должен быть частью контракта реализации. Однако при использовании самого трейта как dyn Trait такой associated type обычно нужно явно зафиксировать, а разные реализации не смогут скрыть разные типы за одним неуточнённым объектом.
В библиотеке есть набор обработчиков, которые должны храниться в одном векторе и вызываться по очереди. Разработчик описывает метод обработки через impl Iterator, поскольку каждая реализация строит свой сложный итератор.
Вариант с impl Iterator хорош для API, где обработчики вызываются через generic-функцию: компилятор сохраняет конкретные типы итераторов и может оптимизировать цепочку вызовов. Но такой вариант не позволяет хранить разные обработчики как Vec<Box<dyn Handler>>.
Вариант с Box<dyn Iterator> совместим с trait object и подходит для гетерогенного списка, но добавляет динамическую диспетчеризацию результата и выделение памяти. Если хранение разных обработчиков является обязательным требованием, выбирают boxed-результат; если важнее производительность и набор типов известен на этапе компиляции, оставляют impl Iterator и generic API.
1. Означает ли impl Trait, что разные вызовы могут возвращать разные типы?
Нет. В возвращаемой позиции impl Trait скрывает один конкретный тип, который функция возвращает во всех ветвях. Поэтому две ветви функции не могут напрямую возвращать разные типы, даже если оба типа реализуют заявленный трейт; для этого нужен общий адаптер, перечисление вариантов или динамический объект.
2. Можно ли заменить impl Trait на generic-параметр результата?
Обычный generic-параметр не позволяет вызывающему выбрать возвращаемый тип произвольно: возвращаемый тип должен быть связан с параметрами функции или трейта. impl Trait предназначен именно для случая, когда конкретный тип выбирает реализация, а вызывающий получает только гарантированный набор bounds.
3. Всегда ли Box<dyn Trait> решает проблему trait object?
Нет. Сам возвращаемый тип должен быть совместим с динамической диспетчеризацией, а исходный трейт должен удовлетворять требованиям dyn-совместимости. Например, наличие generic-методов, неподходящих форм Self, некоторых ассоциированных элементов или других запрещённых конструкций всё равно может сделать dyn Trait недоступным.