dyn всегда указывает на трейт-объект в Rust
dynвсегда указывает на трейт-объект. Безdynвы работаете с статической диспетчеризацией через generics или конкретные типы.
Сравнение: с dyn vs без dyn
Без dyn - статическая диспетчеризация
trait Animal { fn speak(&self); } struct Dog; struct Cat; impl Animal for Dog { fn speak(&self) { println!("Woof!"); } } impl Animal for Cat { fn speak(&self) { println!("Meow!"); } } // Без dyn - generic функция (мономорфизация) fn make_speak_static<T: Animal>(animal: &T) { animal.speak(); // Компилятор генерирует специализированные версии } fn main() { let dog = Dog; let cat = Cat; make_speak_static(&dog); // Вызов Dog::speak make_speak_static(&cat); // Вызов Cat::speak }
С dyn - динамическая диспетчеризация
// С dyn - трейт-объект fn make_speak_dynamic(animal: &dyn Animal) { animal.speak(); // Диспетчеризация через vtable } fn main() { let dog = Dog; let cat = Cat; make_speak_dynamic(&dog as &dyn Animal); make_speak_dynamic(&cat); // Автоматическое приведение к &dyn Animal // Коллекция разных типов через трейт-объекты let animals: Vec<&dyn Animal> = vec![&dog, &cat]; for animal in animals { animal.speak(); // Динамический вызов } }
Ключевые различия
1. Время диспетчеризации
#![allow(unused)] fn main() { // БЕЗ dyn - статическая (compile-time) fn process<T: Animal>(animal: &T) { animal.speak(); // Компилятор знает точный тип T } // С dyn - динамическая (runtime) fn process(animal: &dyn Animal) { animal.speak(); // Тип определяется во время выполнения } }
2. Размер типа
#![allow(unused)] fn main() { // БЕЗ dyn - известный размер fn size_static<T: Animal>() { println!("Size: {}", std::mem::size_of::<T>()); // Известно при компиляции } // С dyn - неизвестный размер (dynamically sized type - DST) fn use_dynamic(animal: &dyn Animal) { // &dyn Animal - это fat pointer (2 указателя) println!("Size of fat pointer: {}", std::mem::size_of::<&dyn Animal>()); } }
3. Использование в коллекциях
#![allow(unused)] fn main() { // НЕВОЗМОЖНО без dyn - разные конкретные типы // let animals: Vec<???> = vec![Dog, Cat]; // Ошибка! // ВОЗМОЖНО с dyn - общий трейт-объект let animals: Vec<Box<dyn Animal>> = vec![ Box::new(Dog), Box::new(Cat), ]; }
Когда dyn обязателен, а когда опционален
Обязателен для трейт-объектов
#![allow(unused)] fn main() { trait MyTrait {} // Эти объявления ТРЕБУЮТ dyn: let obj: &dyn MyTrait; let boxed: Box<dyn MyTrait>; let rc: Rc<dyn MyTrait>; // Функции, принимающие трейт-объекты: fn take_trait_obj(obj: &dyn MyTrait) {} fn return_trait_obj() -> Box<dyn MyTrait> {} }
Не требуется для generics
#![allow(unused)] fn main() { // Без dyn - bounds через generics fn generic_func<T: MyTrait>(item: &T) {} // Или с impl Trait (тоже без dyn) fn impl_func(item: &impl MyTrait) {} fn return_impl() -> impl MyTrait {} }
Практические примеры различий
Пример 1: Производительность
#![allow(unused)] fn main() { use std::time::Instant; trait Calculator { fn compute(&self, x: i32) -> i32; } struct FastCalc; struct SlowCalc; impl Calculator for FastCalc { fn compute(&self, x: i32) -> i32 { x * 2 } } impl Calculator for SlowCalc { fn compute(&self, x: i32) -> i32 { // Имитация тяжелой операции std::thread::sleep(std::time::Duration::from_millis(1)); x * 2 } } // Статическая диспетчеризация (без dyn) fn benchmark_static<T: Calculator>(calc: &T, data: &[i32]) -> Vec<i32> { data.iter().map(|&x| calc.compute(x)).collect() } // Динамическая диспетчеризация (с dyn) fn benchmark_dynamic(calc: &dyn Calculator, data: &[i32]) -> Vec<i32> { data.iter().map(|&x| calc.compute(x)).collect() } }
Пример 2: Гибкость архитектуры
#![allow(unused)] fn main() { trait Plugin { fn execute(&self); } struct PluginA; struct PluginB; impl Plugin for PluginA { fn execute(&self) { println!("Plugin A") } } impl Plugin for PluginB { fn execute(&self) { println!("Plugin B") } } // Без dyn - нужно знать типы на этапе компиляции struct StaticPluginManager<T: Plugin> { plugin: T, } // С dyn - можно загружать плагины динамически struct DynamicPluginManager { plugins: Vec<Box<dyn Plugin>>, } impl DynamicPluginManager { fn add_plugin(&mut self, plugin: Box<dyn Plugin>) { self.plugins.push(plugin); } fn load_from_config(&mut self, config: &str) { // Динамическое создание плагинов на основе конфигурации if config.contains("plugin_a") { self.add_plugin(Box::new(PluginA)); } if config.contains("plugin_b") { self.add_plugin(Box::new(PluginB)); } } } }
Важные нюансы
Автоматическое приведение к dyn
#![allow(unused)] fn main() { let dog = Dog; // Явное указание let animal1: &dyn Animal = &dog; // Автоматическое приведение let animal2: &dyn Animal = &dog; // В функциях тоже работает автоматическое приведение make_speak_dynamic(&dog); // &Dog автоматически приводится к &dyn Animal }
dyn с временами жизни
#![allow(unused)] fn main() { trait Processor<'a> { fn process(&self, data: &'a str) -> &'a str; } // dyn с явным временем жизни fn use_processor<'a>(processor: &dyn Processor<'a>, data: &'a str) -> &'a str { processor.process(data) } }
Заключение
dynВСЕГДА указывает на трейт-объект с динамической диспетчеризацией- Без
dyn- вы используете статическую диспетчеризацию через generics dynобязателен когда вы явно работаете с трейт-объектамиdynне требуется при использовании generics bounds илиimpl Trait
Выбор между dyn и generics зависит от требований к производительности, гибкости архитектуры и необходимости работать с гетерогенными коллекциями.