При создании trait object для трейта с параметром типа как Rust определяет, какой тип параметра закреплён за объектом, и почему один такой объект не может обслуживать разные типы параметра?
Тип параметра трейта должен быть зафиксирован в самом trait object: объект представляет конкретную специализацию трейта, например обработчик байтов, а не обобщённый обработчик любых значений. Поэтому один объект может динамически вызывать только методы выбранной специализации; для разных типов нужны разные объекты, единый тип-обёртка или другая модель API.
Generics позволяют описывать один алгоритм для множества типов, а динамическая диспетчеризация нужна, когда конкретный тип реализации неизвестен на этапе компиляции. Параметризованные трейты объединяют эти подходы: общий контракт может зависеть от типа данных, но при создании trait object этот тип всё равно должен стать известным.
Такое ограничение связано с устройством виртуального вызова. Таблица диспетчеризации должна однозначно знать, какие методы и сигнатуры доступны объекту. Нельзя построить одну таблицу, которая одновременно означает обработку байтов, строк и других несовместимых типов параметра.
Представим канал, который принимает значения определённого типа через трейт с параметром. Если разрешить одному объекту менять этот параметр от вызова к вызову, вызывающий код не смог бы иметь единую статически известную сигнатуру метода.
Попытка использовать один объект для разных специализаций приводит либо к потере типобезопасности, либо к необходимости стирать тип параметра. В Rust это разделено явно: конкретный trait object сохраняет информацию о выбранной специализации, а обобщённость достигается на другом уровне.
Запись dyn Sink<u8> означает: «объект реализует специализацию Sink именно для u8». Вызов через такую ссылку динамически выбирает реализацию Sink<u8>, но не Sink<String> и не любую другую специализацию.
Здесь send не знает конкретный тип Bytes, но знает тип принимаемого значения — u8. Это позволяет Rust сформировать корректный динамический вызов. Сам Bytes теоретически может иметь отдельную реализацию Sink<String>, однако это будет другой trait object с другим закреплённым параметром.
Важно отличать параметр трейта от обобщённого метода. В первом случае параметр выбирается при формировании типа объекта. Во втором один и тот же метод должен работать с произвольным типом при каждом вызове, что обычно несовместимо с trait object, поскольку его таблица диспетчеризации не может содержать бесконечный набор специализаций.
Если требуется хранить в одной коллекции обработчики разных типов, варианты различаются по компромиссам:
Сервис отправляет сообщения через плагины, каждый из которых умеет принимать байты. Интерфейс удобно хранить как Box<dyn Sink<u8>>: сервис динамически выбирает плагин, а контракт данных остаётся единым.
Рассматривались два варианта. Обобщённая функция была бы быстрее и позволила бы компилятору встроить вызовы, но потребовала бы заранее знать конкретный тип плагина. Enum устранил бы динамический вызов, однако сделал бы основной сервис зависимым от полного списка плагинов.
Выбран Box<dyn Sink<u8>>, потому что набор плагинов расширяется независимо от сервиса, а тип сообщения стабилен. Если позднее понадобится передавать строки, это будет отдельный объект dyn Sink<String> либо предварительное преобразование строки в согласованный формат; тот же объект нельзя просто переиспользовать с другим параметром.
Да, если реализации не конфликтуют по правилам согласованности. Например, тип может реализовать Sink<u8> и Sink<String>. Но при использовании через trait object каждая специализация рассматривается отдельно: dyn Sink<u8> и dyn Sink<String> — разные типы представления и разные контракты вызова.
Нет. Параметр трейта сам по себе не запрещает объектное использование. Нужно зафиксировать его значение и чтобы выбранная специализация удовлетворяла требованиям совместимости с trait object. Запрет обычно возникает из-за других особенностей, например generic-методов или методов, возвращающих Self без подходящих ограничений.
Нет. Ассоциированный тип также фиксируется конкретной реализацией и не делает объект полиморфным по нескольким типам. Разница в модели выбора: параметр трейта участвует в выборе специализации реализации, а ассоциированный тип определяется внутри реализации; в обоих случаях trait object имеет один конкретный тип данных для данного контракта.