Menu
Coddy logo textTech

Динамическая диспетчеризация

Часть раздела Объектно-ориентированное программирование путешествия по Rust на Coddy. Урок 45 из 61.

Когда вы используете обобщения с ограничениями трейтов, такими как <T: Summary>, Rust определяет точный тип во время компиляции. Это называется статической диспетчеризацией: компилятор генерирует специализированный код для каждого конкретного типа, который вы используете. Это быстро, но есть ограничение: переменная может содержать только один определённый тип.

Что, если вам нужна единственная переменная, которая во время выполнения могла бы хранить разные типы? Здесь на помощь приходят объекты трейтов. Используя Box<dyn Trait>, вы можете хранить любой тип, реализующий трейт:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

Ключевое слово dyn указывает на динамическую диспетчеризацию: Rust определяет, какой метод вызвать, во время выполнения, а не компиляции. Box необходим, потому что объекты трейтов не имеют известного размера; Box предоставляет указатель фиксированного размера.

Представьте это так: обобщения говорят: «Я работаю с типом T», а объекты трейтов говорят: «Я работаю с чем угодно, что умеет это делать». Компромисс заключается в небольших затратах во время выполнения ради гибкости работы с разными типами через одну и ту же переменную.

challenge icon

Задание

Легко

Давайте создадим систему аренды транспортных средств, демонстрирующую возможности динамической диспетчеризации! Вы создадите трейт, определяющий, что может делать любое транспортное средство, доступное для аренды, а затем используете Box<dyn Trait>, чтобы хранить разные типы транспортных средств в одной переменной.

Вы организуете код в двух файлах:

  • vehicle.rs: Определите публичный трейт Rentable с методом rental_info, который принимает &self и возвращает String. Затем создайте две публичные структуры:
    • Car: с публичным полем model (String). Его метод rental_info должен возвращать Car: {model}
    • Bike: с публичным полем brand (String). Его метод rental_info должен возвращать Bike: {brand}
  • main.rs: Подключите модуль транспортных средств и используйте входные данные для демонстрации динамической диспетчеризации. Создайте переменную Box<dyn Rentable>, которая сначала содержит Car, выведите информацию о его аренде, затем переназначьте ту же переменную так, чтобы она содержала Bike, и снова выведите информацию о его аренде.

В этом и заключается магия: одна переменная типа Box<dyn Rentable> может содержать либо Car, либо Bike: Rust во время выполнения определяет, какой метод rental_info вызвать. Это динамическая диспетчеризация в действии!

В результате должна отображаться информация об обоих транспортных средствах:

Car: {model}
Bike: {brand}

Например, для входных данных Tesla Model 3 и Trek:

Car: Tesla Model 3
Bike: Trek

Вы получите два входных значения: модель автомобиля и марку велосипеда.

Попробуйте сами

mod vehicle;

use vehicle::{Rentable, Car, Bike};

fn main() {
    // Прочитать ввод
    let mut car_model = String::new();
    std::io::stdin().read_line(&mut car_model).expect("Failed to read line");
    let car_model = car_model.trim().to_string();
    
    let mut bike_brand = String::new();
    std::io::stdin().read_line(&mut bike_brand).expect("Failed to read line");
    let bike_brand = bike_brand.trim().to_string();
    
    // TODO: Создайте переменную Box<dyn Rentable>, которая хранит Car
    // Выведите информацию об аренде с помощью метода rental_info()
    
    // TODO: Переназначьте ту же переменную, чтобы она хранила Bike
    // Снова выведите информацию об аренде
    
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Rust