Как приём self по значению влияет на совместимость метода с dyn Trait и возможность его вызова?
Метод с приёмником self по значению не делает трейт несовместимым с dyn Trait, но такой метод нельзя вызвать через &dyn Trait: для этого пришлось бы переместить значение неизвестного размера из-за ссылки. Сам трейт можно использовать как trait object, а методы с &self или &mut self остаются доступными.
Trait objects появились как механизм динамической диспетчеризации: вызывающий код работает с разными реализациями общего трейта через единый интерфейс. Для этого компилятор должен заранее знать, какие методы можно безопасно вызвать через указатель на объект неизвестного конкретного типа.
Правила object safety, в современной терминологии dyn compatibility, исключают методы, для которых динамический вызов невозможно корректно сформировать. Приёмник self по значению является особым случаем: он не требует знания конкретного Self при построении таблицы виртуальных методов, но вызов ограничен владением объектом.
Размер dyn Trait во время компиляции неизвестен, поэтому значение, находящееся за &dyn Trait, нельзя извлечь и переместить из ссылки. Если бы обычный метод с self автоматически запрещал весь трейт, пришлось бы терять полезные трейты только из-за операции, которую некоторые вызывающие стороны всё равно не могут выполнить.
Неверный вывод состоит в том, что наличие метода fn consume(self) гарантирует возможность вызвать его на любой форме trait object. На &dyn Trait владения нет, поэтому такой вызов невозможен; доступными остаются только методы, совместимые с конкретным способом владения объектом.
Приёмник self по значению допускается правилами dyn compatibility. Причина в том, что сам по себе такой метод не требует возвращать или принимать произвольный конкретный тип Self в других позициях сигнатуры. Однако для вызова нужно передать объект во владение, а ссылка &dyn Trait этого не предоставляет.
Метод inspect вызывается через виртуальную диспетчеризацию: из trait object берётся указатель на нужную реализацию. Для consume требуется переместить сам объект, но &dyn Action лишь заимствует его, а конкретный размер и представление объекта скрыты.
Это отличается от метода с приёмником Box<Self>, Rc<Self или другим поддерживаемым владеющим приёмником: такая сигнатура явно описывает, как передаётся владение объектом. При проектировании API следует выбрать форму приёмника по семантике владения, а не добавлять self только ради возможности динамического вызова.
Плагинный API хранит плагины как Box<dyn Plugin>. Метод name(&self) должен вызываться для отображения списка плагинов, а метод shutdown(self) должен окончательно поглощать экземпляр.
Вариант с shutdown(self) сохраняет трейт dyn-compatible, но его вызов через &dyn Plugin невозможен. Если API передаёт плагины именно во владеющем контейнере, можно спроектировать операцию как метод с владеющим приёмником, например self: Box<Self>, чтобы контракт явно отражал передачу Box<dyn Plugin>.
Вариант с &mut self проще для повторного использования объекта, но не выражает его уничтожение через API. Выбор владеющего приёмника оправдан, если после завершения работы объект больше не должен существовать; результатом становится однозначное управление ресурсами без попытки переместить значение из заимствованной ссылки.
Дополнительный вопрос 1: делает ли метод fn consume(self), возвращающий Self, трейт несовместимым с dyn Trait?
Нет, сам приёмник self по значению допустим. Проблемой был бы отдельный тип Self в возвращаемом значении: вызывающий код должен знать конкретный размер результата, а trait object его не раскрывает. Поэтому fn consume(self), fn clone(&self) -> Self и fn make() -> Self имеют разные последствия.
Дополнительный вопрос 2: можно ли вызвать метод с self через Box<dyn Trait>?
Нельзя автоматически делать такой вывод только из наличия Box. Для обычного приёмника self метод требует перемещения значения конкретного типа Self; ссылка на внутренний объект этого не позволяет. Если API должно принимать именно владеющий trait object, сигнатуру обычно выражают явно через поддерживаемый владеющий приёмник, например self: Box<Self>, после чего вызов через Box<dyn Trait> соответствует контракту.
Дополнительный вопрос 3: почему добавление where Self: Sized меняет картину?
Метод с ограничением Self: Sized исключается из интерфейса, доступного через dyn Trait, поскольку для trait object Self не является типом известного фиксированного размера. Такой метод может существовать в трейте и вызываться для конкретных типов, но не должен быть частью виртуального интерфейса trait object. Это полезно, когда трейт содержит операции, требующие конкретного размера или обобщённого поведения, но остальные методы всё ещё должны поддерживать динамическую диспетчеризацию.