При проектировании структуры нужно скрыть конкретный тип поля за трейт-границей. Почему impl Trait нельзя использовать для этого напрямую?
impl Trait нельзя использовать как тип обычного поля структуры, потому что он описывает один скрытый конкретный тип, который компилятор выводит в позиции возвращаемого значения или аргумента функции, но не является самостоятельным именованным типом для хранения. Для поля нужно выбрать другой способ: параметризовать структуру типом, использовать Box<dyn Trait> для динамической диспетчеризации или определить собственный конкретный тип-обёртку.
Подход impl Trait появился как способ скрывать детали конкретного типа, сохраняя статическую диспетчеризацию и оптимизацию мономорфизации. Особенно полезен он для функций, возвращающих сложные типы вроде цепочек адаптеров итераторов, которые неудобно явно записывать в публичном API.
При этом impl Trait не задуман как универсальный механизм объявления анонимных типов в любых местах программы. Его область применения ограничена позициями, где компилятор может связать скрытый тип с конкретной сигнатурой функции.
Поле структуры должно иметь один определённый тип, известный правилам layout, размера и владения. Если разрешить произвольный impl Trait в поле, возникли бы вопросы: какой именно скрытый тип хранится, одинаков ли он у разных конструкторов и как выразить его в типе самой структуры.
Неверный выбор приводит либо к невозможности создать структуру с разными реализациями трейта, либо к потере статической диспетчеризации. Кроме того, Box<dyn Trait> добавляет косвенный доступ и требует совместимости трейта с trait object.
В возвращаемом типе функции impl Trait означает: функция возвращает один конкретный тип, выбранный внутри реализации, но скрытый от вызывающего кода. Все ветви такой функции должны возвращать совместимый скрытый тип; это не означает возможность хранить произвольные реализации трейта.
Для поля есть два основных решения:
Box<dyn Trait> стирает конкретный тип и позволяет хранить разные реализации в одном поле. Вызовы выполняются через динамическую диспетчеризацию, а объект обычно размещается в куче.Минимальный пример:
Здесь make_render скрывает конкретный тип Button только на границе функции. Поле Static::value требует именованный параметр T, а Dynamic::value хранит trait object. Выбор зависит от требований API: статический вариант обычно быстрее и сохраняет больше информации о типе, динамический — гибче для гетерогенных коллекций и стабильнее по размеру типа владельца.
Допустим, обработчик ответа должен хранить форматтер. Если структура всегда работает с одним форматтером, выбирается struct Response<F: Render>, поскольку компилятор мономорфизирует код и может встроить вызовы методов. Минус — тип структуры зависит от F, а коллекция форматтеров разных типов напрямую невозможна.
Если приложение загружает форматтеры из конфигурации и хранит их в одном списке, применяется Box<dyn Render>. Это позволяет объединить разные реализации, но требует object-safe трейта, выделения памяти или иного владения объектом и динамического вызова через таблицу методов.
impl Trait в поле не решает ни одну из этих задач: он не создаёт тип, которым можно явно параметризовать структуру, и не предоставляет стирание типа с динамической диспетчеризацией.
Можно ли заменить поле impl Trait на Box<impl Trait>?
Нет. impl Trait не становится допустимым типом поля от добавления Box. Box требует полноценный тип во внутреннем параметре, а impl Trait в этой позиции не является разрешённым объявлением скрытого типа.
Правильная замена — Box<dyn Render>, если нужна динамическая диспетчеризация, либо Box<T> внутри обобщённой структуры, если конкретный тип должен быть параметром структуры.
Одинаковы ли скрытые типы у двух функций, возвращающих одинаковый impl Trait?
Нет. Каждое возвращаемое impl Trait создаёт отдельный скрытый тип, даже если у функций совпадают трейт и bounds. Результаты таких функций нельзя автоматически считать одним типом или помещать в одну коллекцию без дополнительного стирания типа, например через Box<dyn Trait>.
Что произойдёт, если попытаться хранить разные реализации в struct Holder<T: Render>?
Для каждого конкретного T получится отдельная специализация Holder<T>. Значения Holder<Button> и Holder<Label> имеют разные типы, поэтому их нельзя напрямую поместить в один Vec.
Если единая коллекция обязательна, используют Vec<Box<dyn Render>>, при условии что Render совместим с trait object. Если типы известны заранее и их немного, альтернативой может быть перечисление enum, которое сохраняет статическую диспетчеризацию, но требует явно перечислить варианты.