Динамическая диспетчеризация
Часть раздела Объектно-ориентированное программирование путешествия по 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», а объекты трейтов говорят: «Я работаю с чем угодно, что умеет это делать». Компромисс заключается в небольших затратах во время выполнения ради гибкости работы с разными типами через одну и ту же переменную.
Задание
ЛегкоДавайте создадим систему аренды транспортных средств, демонстрирующую возможности динамической диспетчеризации! Вы создадите трейт, определяющий, что может делать любое транспортное средство, доступное для аренды, а затем используете 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
// Снова выведите информацию об аренде
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media Player9Трейт-объекты
Динамическая диспетчеризацияВекторы трейтовИтерация по трейт-объектамПовторение — Менеджер зоопаркаПотренируйтесь самостоятельно: Онлайн-компилятор Rust