OOP в Rust: инкапсуляция, композиция, полиморфизм
Кратко: В статье объясняются, как сопоставить основные концепции ООП (инкапсуляция, композиция/наследование, полиморфизм, абстракция) с инструментами Rust: модулями, структурами, перечислениями и трейтом. Приведены практические приёмы проектирования, таблица соответствий и контрольные списки для команд.
Зачем читать
- Понять, как реализовать привычные ООП-концепции в Rust без «классов».
- Получить рабочие приёмы и шаблоны для проектирования безопасного и поддерживаемого кода.
Введение
Объектно-ориентированное программирование (ООП) упрощает проектирование ПО, представляя сущности и концепции в виде объектов с состоянием и поведением. Rust не предоставляет встроенной модели классов, но позволяет выразить большинство ООП-идей с помощью встроенных типов: модулей, struct, enum и trait. Rust при этом добавляет строгую модель владения и заимствований, что делает дизайн более явным и безопасным.

Инкапсуляция в Rust
Инкапсуляция — это разделение кода на автономные единицы, которые скрывают внутренние детали и открывают наружный интерфейс. В Rust инкапсуляция достигается через модули и управление видимостью (private/public).
- Модуль (module) — коллекция элементов: функций, структур, перечислений и констант.
- По умолчанию элементы модуля приватные. Чтобы сделать элемент доступным извне, используют ключевое слово pub.
Объявление модуля
mod my_module {
// module items go here
}
Модули можно вкладывать:
mod parent_module {
mod my_module {
// module items go here
}
}
Доступ к вложенным модулям осуществляется через двойной двоеточие, например: parent_module::my_module.
Видимость и pub
mod my_module {
pub fn my_function() {
// function body goes here
}
}
Теперь my_function доступна из других частей программы. Это позволяет контролировать публичный API модуля и скрывать вспомогательные детали.
Трейты: интерфейсы и поведение
Трейты в Rust задают набор методов (поведение), которые может реализовать тип. Это основной способ описать интерфейс, аналогичен интерфейсам/абстрактным классам в других языках.
pub trait Printable {
fn print(&self);
}
pub struct MyType {
// struct fields here
}
impl Printable for MyType {
fn print(&self) {
// implementation here
}
}
Трейты позволяют писать обобщённый код, который работает с любыми типами, реализующими требуемое поведение.
Композиция вместо наследования
Традиционное наследование (subclassing) подразумевает автоматическую передачу свойств и методов от родителя к потомку. Rust не поощряет древовидное наследование: вместо этого используют композицию и делегирование.
Композиция — создание нового типа за счёт включения других типов в поле структуры. Это даёт гибкость и явность в управлении зависимостями.
Примеры enum и struct
enum Animal {
Cat,
Dog,
Bird,
// ...
}
Или struct с полями:
struct Animal {
name: String,
age: u8,
animal_type: AnimalType,
}
enum AnimalType {
Cat,
Dog,
Bird,
// ...
}
Структура Animal содержит значение перечисления AnimalType — так делается композиция данных.
Поведение через трейты (аналог «наследования поведения»)
trait Fly {
fn fly(&self);
}
Реализация трейта Fly для разных типов:
struct Bird {
name: String,
wingspan: f32,
}
impl Fly for Bird {
fn fly(&self) {
println!("{} is flying!", self.name);
}
}
struct Plane {
model: String,
max_speed: u32,
}
impl Fly for Plane {
fn fly(&self) {
println!("{} is flying!", self.model);
}
}
Программист может объединять объекты с общим поведением в коллекцию trait-объектов:
fn main() {
let bird = Bird {
name: String::from("Eagle"),
wingspan: 2.0,
};
let plane = Plane {
model: String::from("Boeing 747"),
max_speed: 900,
};
let flying_objects: Vec<&dyn Fly> = vec![&bird, &plane];
for object in flying_objects {
object.fly();
}
}

Полиморфизм в Rust
Полиморфизм — способность разных типов предоставлять единый интерфейс. В Rust это достигается трейтом и двумя основными подходами к обобщённости:
- Параметрический полиморфизм через дженерики (compile-time).
- Полиморфизм времени выполнения через trait-объекты (
dyn Trait).
Пример трейта Drawable:
trait Drawable {
fn draw(&self);
}
Реализация для конкретного типа:
struct Rectangle {
width: u32,
height: u32,
}
impl Drawable for Rectangle {
fn draw(&self) {
// Render the rectangle on the screen
}
}
Генерик-функция, принимающая любой Drawable:
fn draw_object(object: &T) {
object.draw();
}
Преимущество дженериков — отсутствие виртуального вызова в рантайме; trait-объекты позволяют хранить разные реализации в одном контейнере, но требуют динамической диспетчеризации.
Абстракция через трейты
Абстракция скрывает детали реализации, предоставляя понятный интерфейс. В Rust это именно область применения трейтов.
trait Media {
fn play(&self);
}
Реализация для Song:
struct Song {
title: String,
artist: String,
}
impl Media for Song {
fn play(&self) {
println!("Playing song: {} by {}", self.title, self.artist);
}
}
Пример использования в main:
fn main() {
let song = Song {
title: String::from("Bohemian Rhapsody"),
artist: String::from("Queen"),
};
song.play();
}

Практическая методология: как проектировать OOP-подобную архитектуру в Rust
- Определите сущности данных (struct/enum).
- Опишите поведение через трейты (интерфейсы).
- Выберите форму полиморфизма: дженерики для производительности или trait-объекты для гибкости.
- Используйте модули для инкапсуляции API и скрытия реализации.
- Предпочитайте композицию и делегирование вместо глубоких иерархий наследования.
- Документируйте ограничения владения и области жизни (lifetimes) в API.
Контрольная таблица «быстрое решение» (cheat sheet)
- Классы/объекты → struct + impl блоки.
- Интерфейсы → trait.
- Наследование свойств → вложение struct (composition) + делегирование.
- Полиморфизм во времени компиляции → generics (T: Trait).
- Полиморфизм в рантайме → &dyn Trait или Box
. - Скрытие реализации → модуль + приватные функции/поля.
Когда паттерн ООП не подходит (примеры)
- Глубокие иерархии классов: в Rust лучше реорганизовать через композицию и поведение по контрактам (traits).
- Сильная мутабельность и разделение состояния между наследниками: владение и заимствование Rust вынуждают делать зависимости явными.
- Требуется сложная множественная диспетчеризация: лучше использовать явную стратегию или сопоставление с образцом (match по enum).
Альтернативные подходы и шаблоны
- Функциональный стиль: чистые функции и неизменяемые структуры вместо объектов со состоянием.
- Entity-Component-System (ECS): полезно в играх и симуляциях, заменяет иерархии объектов композиционной моделью компонентов.
- Actor-модель: для систем с сообщениями и изоляцией состояния.
Mental model (короткие эвристики)
- «Трейты — это контракты поведения».
- «Struct — контейнер данных, impl — поведение».
- «Композиция = явное владение зависимостями».
- «Дженерики = эффективность, dyn Trait = гибкость».
Роль‑ориентированные чек‑листы
Разработчик:
- Описал публичный API через модули и pub.
- Разбил поведение на трейты с ясной семантикой.
- Выбрал между generics и trait-объектами и задокументировал выбор.
Код‑ревьювер:
- Проверил отсутствие утечек владения и ненужных клонирований.
- Убедился, что приватные детали скрыты и API минимален.
- Оценил стоимость динамической диспетчеризации там, где используются trait-объекты.
Архитектор:
- Оценил альтернативы композиции и ECS для масштабируемости.
- Убедился, что паттерны соответствуют требованиям производительности и поддерживаемости.
Таблица соответствий: ООП-концепция → Rust
- Инкапсуляция → модули + pub/private.
- Наследование (поведение) → трейты + impl для типов.
- Наследование (состояние) → композиция struct внутри struct.
- Абстракция → трейты и скрытие деталей в модулях.
- Полиморфизм → generics или trait-объекты.
Критерии приёмки
- API модуля минимален и документирован.
- Внутренние детали не доступны извне (private).
- Трейты имеют однозначную семантику и не смешивают обязанности.
- Выбранный тип полиморфизма обоснован (производительность vs гибкость).
1‑строчный глоссарий
- struct — контейнер полей.
- enum — перечисление дискретных вариантов.
- trait — контракт/интерфейс поведения.
- impl — реализация методов для типа.
- dyn Trait — trait-объект для полиморфизма в рантайме.
- pub — делает элемент видимым вне модуля.
Риски и рекомендации
- Риск: чрезмерное использование trait-объектов может привести к перегрузке виртуальными вызовами. Рекомендация: профилируйте и используйте дженерики, если критичен throughput.
- Риск: сложные правила владения усложняют миграции. Рекомендация: документируйте инварианты и области жизни.
Короткое резюме
Rust не даёт «классов» в привычном виде, но его инструменты позволяют выразить OOP-принципы более надёжно и явственно. Используйте модули для инкапсуляции, трейты для контракта поведения и композицию вместо наследования данных. Выбор между generics и trait-объектами — архитектурный: выбирайте в зависимости от требований по производительности и гибкости.
Важно: при переносе привычных ООП-дизайнов в Rust стоит пересмотреть архитектуру под модель владения и заимствований — это источник безопасности и ясности.
Похожие материалы
Как просмотреть и удалить историю инкогнито
Изменить email в аккаунте Microsoft на Windows 10
Отключить клавиатуру ноутбука в Windows 10/11
Сохранить PDF на главный экран iPhone
Исправить ошибку прокси в Chrome быстро