Статическая и динамическая диспетчеризация
Компромиссы
«Статическая и динамическая диспетчеризация» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 4 уроков всего.
Два способа диспетчеризации
При вызове метода трейта Rust должен определить, какая конкретная реализация будет выполнена. Есть две стратегии: статическая диспетчеризация (решение во время компиляции) и динамическая диспетчеризация (решение во время выполнения).
Статическая диспетчеризация с обобщёнными типами
Обобщённые функции используют статическую диспетчеризацию. Компилятор создаёт специализированную копию для каждого конкретного типа — этот процесс называется мономорфизацией.
trait Area { fn area(&self) -> f64; }
struct Square { s: f64 }
impl Area for Square { fn area(&self) -> f64 { self.s * self.s } }
fn print_area<T: Area>(shape: &T) {
println!("{}", shape.area());
}
fn main() {
print_area(&Square { s: 4.0 });
}Мономорфизация
Для каждого типа, с которым вызывается функция, компилятор создаёт её отдельную версию. Вызов метода превращается в прямой вызов без поиска, поэтому он такой же быстрый, как код, написанный вручную.
Динамическая диспетчеризация с dyn
Объекты трейтов используют динамическую диспетчеризацию. Одна функция обрабатывает все типы, а адрес метода находится во время выполнения через таблицу виртуальных методов.
trait Area { fn area(&self) -> f64; }
struct Square { s: f64 }
impl Area for Square { fn area(&self) -> f64 { self.s * self.s } }
fn print_area(shape: &dyn Area) {
println!("{}", shape.area());
}
fn main() {
print_area(&Square { s: 4.0 });
}Таблица виртуальных методов
Объект трейта — это толстый указатель: одна его часть указывает на данные, а другая — на таблицу виртуальных методов со списком адресов методов. Каждый вызов обращается к нужному элементу этой таблицы.
Это косвенное обращение и есть цена во время выполнения.
Компромисс по скорости
Статическая диспетчеризация быстрее при каждом вызове и допускает встраивание, но создаёт больше машинного кода. Динамическая диспетчеризация добавляет крошечное косвенное обращение, зато сохраняет небольшой размер кода.
Для большинства приложений разница несущественна; выбирайте исходя из требуемой гибкости.
Компромисс по размеру кода
Вызов обобщённой функции с множеством типов создаёт множество копий, что может раздуть двоичный файл. Одна функция с dyn избегает такого дублирования.
Поэтому библиотеки иногда используют объекты трейтов внутри себя.
Компромисс по гибкости
Обобщённые типы требуют одного конкретного типа в каждом месте вызова, поэтому они не могут хранить смешанные типы в коллекции. Объекты трейтов могут. Если нужен разнородный список, ответом будет динамическая диспетчеризация.
trait Area { fn area(&self) -> f64; }
struct Square { s: f64 }
struct Rect { w: f64, h: f64 }
impl Area for Square { fn area(&self) -> f64 { self.s * self.s } }
impl Area for Rect { fn area(&self) -> f64 { self.w * self.h } }
fn main() {
let shapes: Vec<Box<dyn Area>> = vec![Box::new(Square { s: 2.0 }), Box::new(Rect { w: 3.0, h: 4.0 })];
let total: f64 = shapes.iter().map(|s| s.area()).sum();
println!("{}", total);
}Сочетание обоих подходов
Их можно сочетать: обобщённая функция может принимать impl Trait на границе и внутри хранить значения как Box<dyn Trait>. Используйте каждый подход там, где он уместен.
Руководство по выбору
Краткие правила:
- Один тип на вызов, горячий участок? Используйте обобщённые типы (статическая диспетчеризация).
- Смешанные типы в коллекции? Используйте
dyn(динамическая диспетчеризация). - Беспокоит размер двоичного файла? Склоняйтесь к
dyn.
Оба подхода бесплатны там, где это важно
Rust никогда не добавляет накладные расходы, которых вы не просили. Обобщённые типы не стоят ничего во время выполнения; объекты трейтов требуют только одного косвенного обращения через указатель. Вы явно выбираете компромисс.
Быстрая проверка
Проверьте свои знания о диспетчеризации.
Итоги
Вы сравнили два вида диспетчеризации:
- Статическая (обобщённые типы): мономорфизация, высокая скорость, больший двоичный файл, один тип на вызов
- Динамическая (
dyn): поиск в таблице виртуальных методов, гибкость, меньший объём кода, поддержка смешанных коллекций - Выбирайте исходя из требований к гибкости, производительности и размеру двоичного файла
Часто задаваемые вопросы
Урок «Статическая и динамическая диспетчеризация» бесплатный?
Да — полный текст урока «Статическая и динамическая диспетчеризация» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.
Чему я научусь в уроке «Статическая и динамическая диспетчеризация»?
Компромиссы Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Learn Rust Coding?
Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Статическая и динамическая диспетчеризация»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Learn Rust Coding?
Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Определение трейтов
- Объекты трейтов и dyn
- Статическая и динамическая диспетчеризация
- Методы по умолчанию