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