Паттерны проектирования в Rust на продвинутом уровне: примеры кода

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Паттерны проектирования в Rust: продвинутый уровень с примерами

Паттерны проектирования — это проверенные решения типовых задач, которые помогают создавать гибкую, поддерживаемую и масштабируемую архитектуру. В Rust, с его системой владения, заимствования и типажами, многие классические паттерны приобретают уникальную реализацию. В этой статье мы разберём продвинутые паттерны проектирования, которые особенно эффективны в экосистеме Rust.

1. Продвинутый Builder (Строитель)

Классический паттерн Builder в Rust часто реализуется через цепочку методов, возвращающих &mut Self. Однако на продвинутом уровне мы используем типизированный Builder с типами-состояниями для обеспечения корректности на этапе компиляции.

Проблема: контроль обязательных полей

Обычный Builder не гарантирует, что пользователь заполнил все обязательные поля. Мы можем забыть вызвать .build() или не задать критически важный параметр.

Решение: Builder с типами-фантомами

use std::marker::PhantomData;

// Типы-состояния для полей
struct UsernameNotSet;
struct UsernameSet;
struct PasswordNotSet;
struct PasswordSet;

struct UserBuilder<U, P> {
    username: Option<String>,
    password: Option<String>,
    _username: PhantomData<U>,
    _password: PhantomData<P>,
}

impl UserBuilder<UsernameNotSet, PasswordNotSet> {
    pub fn new() -> Self {
        Self {
            username: None,
            password: None,
            _username: PhantomData,
            _password: PhantomData,
        }
    }
}

impl<P> UserBuilder<UsernameNotSet, P> {
    pub fn username(mut self, name: &str) -> UserBuilder<UsernameSet, P> {
        self.username = Some(name.to_string());
        UserBuilder {
            username: self.username,
            password: self.password,
            _username: PhantomData,
            _password: PhantomData,
        }
    }
}

impl<U> UserBuilder<U, PasswordNotSet> {
    pub fn password(mut self, pwd: &str) -> UserBuilder<U, PasswordSet> {
        self.password = Some(pwd.to_string());
        UserBuilder {
            username: self.username,
            password: self.password,
            _username: PhantomData,
            _password: PhantomData,
        }
    }
}

impl UserBuilder<UsernameSet, PasswordSet> {
    pub fn build(self) -> User {
        User {
            username: self.username.expect("Username must be set"),
            password: self.password.expect("Password must be set"),
        }
    }
}

pub struct User {
    pub username: String,
    pub password: String,
}

fn main() {
    // Теперь компилятор не даст вызвать build() без установки обоих полей
    let user = UserBuilder::new()
        .username("alice")
        .password("secret123")
        .build();
    println!("User: {}", user.username);
}

Преимущество: ошибка "забыл поле" обнаруживается на этапе компиляции, а не в рантайме.

2. Паттерн State (Состояние) через трейты

Паттерн State позволяет объекту менять своё поведение при изменении внутреннего состояния. В Rust это элегантно реализуется через перечисления (enum) и трейты.

Реализация с enum

enum ConnectionState {
    Disconnected,
    Connecting,
    Connected,
}

struct Connection {
    state: ConnectionState,
}

impl Connection {
    pub fn new() -> Self {
        Self { state: ConnectionState::Disconnected }
    }

    pub fn connect(&mut self) {
        match self.state {
            ConnectionState::Disconnected => {
                println!("Начинаем подключение...");
                self.state = ConnectionState::Connecting;
            }
            ConnectionState::Connecting => {
                println!("Уже подключаемся, ждите.");
            }
            ConnectionState::Connected => {
                println!("Уже подключены.");
            }
        }
    }

    pub fn disconnect(&mut self) {
        match self.state {
            ConnectionState::Connected => {
                println!("Отключаемся...");
                self.state = ConnectionState::Disconnected;
            }
            _ => println!("Не в состоянии Connected, отключение невозможно."),
        }
    }
}

fn main() {
    let mut conn = Connection::new();
    conn.connect(); // Начинаем подключение
    conn.connect(); // Уже подключаемся
    conn.disconnect(); // Отключаемся
}

Продвинутая версия с трейтами

trait State {
    fn connect(self: Box<Self>) -> Box<dyn State>;
    fn disconnect(self: Box<Self>) -> Box<dyn State>;
    fn send_data(&self, data: &str);
}

struct Disconnected;
struct Connecting;
struct Connected {
    session_id: u32,
}

impl State for Disconnected {
    fn connect(self: Box<Self>) -> Box<dyn State> {
        println!("Подключаемся...");
        Box::new(Connecting)
    }
    fn disconnect(self: Box<Self>) -> Box<dyn State> {
        println!("Уже отключены.");
        self
    }
    fn send_data(&self, _data: &str) {
        println!("Не можем отправить данные: не подключены.");
    }
}

impl State for Connecting {
    fn connect(self: Box<Self>) -> Box<dyn State> {
        println!("Уже в процессе подключения.");
        self
    }
    fn disconnect(self: Box<Self>) -> Box<dyn State> {
        println!("Отменяем подключение.");
        Box::new(Disconnected)
    }
    fn send_data(&self, _data: &str) {
        println!("Подключение ещё не завершено.");
    }
}

impl State for Connected {
    fn connect(self: Box<Self>) -> Box<dyn State> {
        println!("Уже подключены.");
        self
    }
    fn disconnect(self: Box<Self>) -> Box<dyn State> {
        println!("Отключаемся...");
        Box::new(Disconnected)
    }
    fn send_data(&self, data: &str) {
        println!("Отправляем данные '{}' через сессию {}", data, self.session_id);
    }
}

struct BetterConnection {
    state: Box<dyn State>,
}

impl BetterConnection {
    fn new() -> Self {
        BetterConnection { state: Box::new(Disconnected) }
    }
    fn connect(&mut self) {
        let old_state = std::mem::replace(&mut self.state, Box::new(Disconnected));
        self.state = old_state.connect();
    }
    fn disconnect(&mut self) {
        let old_state = std::mem::replace(&mut self.state, Box::new(Disconnected));
        self.state = old_state.disconnect();
    }
    fn send(&self, data: &str) {
        self.state.send_data(data);
    }
}

fn main() {
    let mut conn = BetterConnection::new();
    conn.send("hello"); // Не можем
    conn.connect();
    conn.send("hello"); // Подключение не завершено
    // Здесь бы мы дождались подключения, но для примера пропустим
}

Блоги

Рекомендаций по книгам