Динамическая диспетчеризация
Часть раздела 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Вы получите два входных значения: модель автомобиля и марку велосипеда.
Шпаргалка
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 указывает на динамическую диспетчеризацию — вызовы методов разрешаются во время выполнения. Box необходим, потому что объекты типажей не имеют известного размера во время компиляции.
Ключевое отличие: Обобщения говорят: «Я работаю с типом T» (одним конкретным типом), в то время как объекты типажей говорят: «Я работаю с чем угодно, что реализует этот типаж» (несколько типов через одну и ту же переменную). Платой за гибкость являются небольшие накладные расходы во время выполнения.
Попробуйте сами
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Трейт-объекты
Динамическая диспетчеризацияВекторы трейтовИтерация по трейт-объектамПовторение — Менеджер зоопарка