Можно ли хранить значение типа dyn Trait непосредственно в локальной переменной, и какой механизм определяет допустимый способ хранения?
Непосредственно хранить значение типа dyn Trait нельзя: это динамически определяемый, или unsized, тип, размер которого неизвестен в месте компиляции. Его хранят за косвенным уровнем — например, через &dyn Trait, Box<dyn Trait>, Rc<dyn Trait> или Arc<dyn Trait>.
Указатель на trait object обычно содержит две части: адрес конкретного значения и адрес таблицы виртуальных методов (vtable). Благодаря этому Rust может вызвать реализацию метода во время выполнения.
Подход с trait objects решает задачу динамического полиморфизма: программе нужно работать с объектами разных конкретных типов через общий интерфейс, не зная заранее их точные типы.
Вместо хранения значений разного размера «как есть» Rust использует косвенное хранение. Это сохраняет требование языка о том, что обычные локальные переменные, элементы массивов и значения контейнеров имеют известный размер.
Представим несколько типов, реализующих один trait. Один объект может занимать несколько байт, другой — существенно больше, поэтому общий статический размер для значения dyn Trait определить нельзя.
Если попытаться хранить такой объект непосредственно, компилятор не сможет вычислить размер локальной переменной, смещение следующего элемента контейнера или способ передачи значения по значению. Ошибка проявится на этапе компиляции, а не во время выполнения.
dyn Trait описывает не конкретную структуру, а пару из конкретного значения и доступного ему набора реализаций методов. Сам тип является широким типом и не имеет фиксированного размера, поэтому обычно используется только за указателем или ссылкой.
Box<dyn Render> имеет известный размер, потому что сам Box хранит указатель. Конкретное значение Button находится в выделенной области памяти, а вызов view.render() выбирает реализацию через vtable.
Косвенность не означает обязательное выделение в куче для всех случаев. Ссылку &dyn Render можно получить на объект, который живёт на стеке, если его время жизни достаточно велико. Box выбирают, когда trait object должен владеть значением или его нужно поместить в структуру либо коллекцию с динамическим временем жизни.
Динамический вызов обычно менее предсказуем для оптимизатора, чем вызов через обобщённый параметр с мономорфизацией. Зато trait object позволяет хранить разные реализации в одной коллекции и уменьшает связанность кода с конкретными типами.
Не любой trait можно использовать как trait object. Его методы и связанные элементы должны удовлетворять правилам object safety, которые в современной терминологии Rust описываются как требования к dyn-compatible trait. Например, методы, возвращающие Self без подходящих ограничений, могут сделать trait несовместимым с dyn Trait.
Сервис рендеринга должен хранить набор компонентов, типы которых заранее неизвестны: кнопки, изображения и пользовательские виджеты. Рассматривались два варианта.
Vec<Box<dyn Render>> позволяет добавлять разные типы, подключать новые реализации без изменения перечисления и вызывать методы динамически. Недостатки — косвенный доступ, возможные выделения памяти и динамическая диспетчеризация.
Альтернативой был бы enum Widget со всеми вариантами. Он обеспечивает статический match, не требует vtable и часто лучше оптимизируется, но каждое добавление типа требует изменения перечисления и его обработчиков.
Если набор компонентов открыт для расширения, выбирают Vec<Box<dyn Render>>. В результате коллекция хранит значения единообразно через указатели, а код сервиса не зависит от конкретных реализаций. Для закрытого и небольшого набора типов предпочтительнее enum.
Вопрос: Обязательно ли объект, используемый как dyn Trait, должен находиться в куче?
Ответ: Нет. В куче находится значение при использовании Box<dyn Trait>, но ссылка &dyn Trait может указывать на объект, живущий на стеке или в другой области памяти. Ограничение относится не к месту хранения конкретной реализации, а к тому, что сам trait object нельзя хранить без косвенного уровня.
Вопрос: Почему Vec<dyn Trait> недопустим, а Vec<Box<dyn Trait>> допустим?
Ответ: Vec должен знать размер и выравнивание каждого элемента, чтобы вычислять адрес элемента по его индексу. dyn Trait не имеет фиксированного размера, поэтому непосредственно элементом Vec быть не может. Box<dyn Trait> имеет фиксированный размер указателя, а сами значения хранятся отдельно.
Вопрос: Автоматически ли dyn Trait поддерживает Clone и Send, если конкретные реализации поддерживают их?
Ответ: Нет. Trait object имеет только те возможности, которые гарантированы его типом и ограничениями. Для передачи между потоками обычно требуется явно использовать dyn Trait + Send или dyn Trait + Send + Sync; при этом все фактические реализации должны удовлетворять этим auto traits. Clone нельзя просто вызвать через dyn Trait, если интерфейс не спроектирован специально для клонирования trait object, поскольку стандартный Clone возвращает Self и не является напрямую dyn-compatible.