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

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

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

Начать курс

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

Rust — это язык, который меняет подход к проектированию. Классические паттерны из GoF часто требуют адаптации из-за строгой системы владения, времени жизни и отсутствия наследования. В этой статье мы разберем продвинутые паттерны проектирования, которые не просто работают в Rust, но используют его сильные стороны: безопасность памяти, zero-cost абстракции и мощную систему типов.

1. Паттерн Builder с проверками на этапе компиляции

Классический Builder в Rust обычно реализуется через структуру с опциональными полями. На продвинутом уровне мы можем использовать систему типов, чтобы проверять обязательные поля на этапе компиляции. Это исключает runtime-ошибки "забыл установить обязательное поле".

Реализация через Type State (состояние типа) позволяет сделать Builder безопасным и самодокументируемым.

// Состояния для Builder
struct Empty;
struct WithName(String);
struct WithAge(u8);
struct Ready(WithName, WithAge);

struct PersonBuilder<State> {
    state: State,
}

impl PersonBuilder<Empty> {
    pub fn new() -> Self {
        PersonBuilder { state: Empty }
    }
    
    pub fn name(self, name: &str) -> PersonBuilder<WithName> {
        PersonBuilder {
            state: WithName(name.to_string()),
        }
    }
}

impl PersonBuilder<WithName> {
    pub fn age(self, age: u8) -> PersonBuilder<Ready> {
        PersonBuilder {
            state: Ready(self.state, WithAge(age)),
        }
    }
}

impl PersonBuilder<Ready> {
    pub fn build(self) -> Person {
        Person {
            name: self.state.0 .0,
            age: self.state.1 .0,
        }
    }
}

struct Person {
    name: String,
    age: u8,
}

fn main() {
    // Компилируется - все поля установлены
    let person = PersonBuilder::new()
        .name("Alice")
        .age(30)
        .build();
    
    // Не скомпилируется: нет name()
    // let person = PersonBuilder::new().age(30).build();
    
    println!("{} is {} years old", person.name, person.age);
}

Преимущество: ошибки конфигурации отлавливаются на этапе компиляции, а не во время выполнения. Это особенно ценно для сложных объектов с десятками параметров.

2. Паттерн RAII и умные указатели для управления ресурсами

RAII (Resource Acquisition Is Initialization) — это не просто паттерн, а философия Rust. Каждый ресурс (память, файл, сокет, блокировка) должен быть захвачен в конструкторе и освобожден в деструкторе. Rust реализует это через трейты Drop и систему владения.

Продвинутый пример — создание RAII-обертки для мьютекса с автоматическим освобождением.

use std::ops::Deref;
use std::sync::{Arc, Mutex};

struct Guard<T> {
    data: T,
}

impl<T> Drop for Guard<T> {
    fn drop(&mut self) {
        println!("Ресурс освобожден!");
    }
}

impl<T> Deref for Guard<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.data
    }
}

struct ResourceManager<T> {
    resource: Arc<Mutex<T>>,
}

impl<T> ResourceManager<T> {
    fn new(data: T) -> Self {
        ResourceManager {
            resource: Arc::new(Mutex::new(data)),
        }
    }
    
    fn acquire(&self) -> Guard<std::sync::MutexGuard<T>> {
        let guard = self.resource.lock().unwrap();
        Guard { data: guard }
    }
}

fn main() {
    let manager = ResourceManager::new(42);
    {
        let guard = manager.acquire();
        println!("Значение: {}", *guard);
        // guard выходит из области видимости -> автоматически освобождает мьютекс
    } // Печатает "Ресурс освобожден!"
    
    println!("Мьютекс снова доступен");
}

Ключевая идея: деструктор вызывается автоматически при выходе переменной из области видимости. Это гарантирует освобождение ресурсов даже в случае паники или раннего возврата.

3. Паттерн Strategy через трейты и замыкания

В Rust паттерн Strategy элегантно реализуется через трейты или замыкания. В отличие от классического ООП, здесь нет наследования — используется статическая диспетчеризация (monomorphization) или динамическая (через Box<dyn Trait>).

Пример: система сжатия данных с разными алгоритмами.

// Трейт для стратегии сжатия
trait CompressionStrategy {
    fn compress(&self, data: &[u8]) -> Vec<u8>;
    fn decompress(&self, data: &[u8]) -> Vec<u8>;
}

// Конкретные стратегии
struct GzipStrategy;
impl CompressionStrategy for GzipStrategy {
    fn compress(&self, data: &[u8]) -> Vec<u8> {
        // Имитация сжатия
        data.iter().map(|&b| b.wrapping_add(1)).collect()
    }
    
    fn decompress(&self, data: &[u8]) -> Vec<u8> {
        data.iter().map(|&b| b.wrapping_sub(1)).collect()
    }
}

struct NoCompression;
impl CompressionStrategy for NoCompression {
    fn compress(&self, data: &[u8]) -> Vec<u8> {
        data.to_vec()
    }
    
    fn decompress(&self, data: &[u8]) -> Vec<u8> {
        data.to_vec()
    }
}

// Контекст, использующий стратегию
struct DataCompressor<S: CompressionStrategy> {
    strategy: S,
}

impl<S: CompressionStrategy> DataCompressor<S> {
    fn new(strategy: S) -> Self {
        DataCompressor { strategy }
    }
    
    fn process(&self, data: &[u8]) -> Vec<u8> {
        self.strategy.compress(data)
    }
}

// Альтернатива с замыканиями
fn process_with_closure<F>(data: &[u8], compressor: F) -> Vec<u8>
where
    F: Fn(&[u8]) -> Vec<u8>,
{
    compressor(data)
}

fn main() {
    let data = b"Hello, Rust!";
    
    // Статическая диспетчеризация
    let gzip_compressor = DataCompressor::new(GzipStrategy);
    let compressed = gzip_compressor.process(data);
    println!("Сжато Gzip: {:?}", compressed);
    
    // Динамическая диспетчеризация
    let strategies: Vec<Box<dyn CompressionStrategy>> = vec![
        Box::new(GzipStrategy),
        Box::new(NoCompression),
    ];
    
    for strategy in strategies {
        let result = strategy.compress(data);
        println                

Блоги

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