Behavioral Patterns

Patterns that focus on communication between objects:

Iterator Pattern

The iterator pattern provides a way to access elements of a collection sequentially:

struct Counter {
    count: usize,
    limit: usize,
}

impl Counter {
    fn new(limit: usize) -> Self {
        Counter { count: 0, limit }
    }
}

impl Iterator for Counter {
    type Item = usize;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.count < self.limit {
            let current = self.count;
            self.count += 1;
            Some(current)
        } else {
            None
        }
    }
}

fn main() {
    let counter = Counter::new(5);
    
    // Use the iterator
    for count in counter {
        println!("Count: {}", count);
    }
    
    // Create another counter and use iterator methods
    let sum: usize = Counter::new(10).sum();
    println!("Sum: {}", sum);
    
    let even_counts: Vec<_> = Counter::new(10).filter(|&n| n % 2 == 0).collect();
    println!("Even counts: {:?}", even_counts);
}

Observer Pattern

The observer pattern allows objects to be notified of changes:

use std::cell::RefCell;
use std::rc::{Rc, Weak};

// Observer trait
trait Observer {
    fn update(&self, message: &str);
}

// Concrete observer
struct ConcreteObserver {
    name: String,
}

impl ConcreteObserver {
    fn new(name: &str) -> Self {
        ConcreteObserver {
            name: name.to_string(),
        }
    }
}

impl Observer for ConcreteObserver {
    fn update(&self, message: &str) {
        println!("Observer {} received: {}", self.name, message);
    }
}

// Subject that notifies observers
struct Subject {
    observers: RefCell<Vec<Weak<dyn Observer>>>,
}

impl Subject {
    fn new() -> Self {
        Subject {
            observers: RefCell::new(Vec::new()),
        }
    }
    
    fn attach(&self, observer: Weak<dyn Observer>) {
        self.observers.borrow_mut().push(observer);
    }
    
    fn notify(&self, message: &str) {
        // Clean up any dropped observers
        self.observers.borrow_mut().retain(|o| o.upgrade().is_some());
        
        // Notify all active observers
        for observer in self.observers.borrow().iter() {
            if let Some(o) = observer.upgrade() {
                o.update(message);
            }
        }
    }
}

Strategy Pattern

The strategy pattern defines a family of algorithms and makes them interchangeable:

// Strategy trait
trait SortStrategy {
    fn sort(&self, data: &mut [i32]);
}

// Concrete strategies
struct BubbleSort;
impl SortStrategy for BubbleSort {
    fn sort(&self, data: &mut [i32]) {
        println!("Sorting using bubble sort");
        // Bubble sort implementation
        let n = data.len();
        for i in 0..n {
            for j in 0..n - i - 1 {
                if data[j] > data[j + 1] {
                    data.swap(j, j + 1);
                }
            }
        }
    }
}

struct QuickSort;
impl SortStrategy for QuickSort {
    fn sort(&self, data: &mut [i32]) {
        println!("Sorting using quick sort");
        // Quick sort implementation (simplified)
        if data.len() <= 1 {
            return;
        }
        
        // Simplified implementation for demonstration
        data.sort();
    }
}

// Context that uses a strategy
struct Sorter {
    strategy: Box<dyn SortStrategy>,
}

impl Sorter {
    fn new(strategy: Box<dyn SortStrategy>) -> Self {
        Sorter { strategy }
    }
    
    fn set_strategy(&mut self, strategy: Box<dyn SortStrategy>) {
        self.strategy = strategy;
    }
    
    fn sort(&self, data: &mut [i32]) {
        self.strategy.sort(data);
    }
}

Rust-Specific Idioms

Patterns that are particularly idiomatic in Rust:

Newtype Pattern

The newtype pattern creates a new type that wraps an existing type, providing type safety and encapsulation:

// Newtype for user IDs
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UserId(u64);

// Newtype for product IDs
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct ProductId(u64);

// Functions that accept specific types
fn get_user(id: UserId) -> Option<User> {
    // Look up user by ID
    Some(User { id, name: "Alice".to_string() })
}

fn get_product(id: ProductId) -> Option<Product> {
    // Look up product by ID
    Some(Product { id, name: "Widget".to_string() })
}

// User and Product structs
struct User {
    id: UserId,
    name: String,
}

struct Product {
    id: ProductId,
    name: String,
}