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 зависит от требований к производительности, гибкости архитектуры и необходимости работать с гетерогенными коллекциями.