Design Patterns in Rust with Code Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course
Here is the translated HTML content with all tags preserved and only the text content translated to English: ```html

Design Patterns in Rust: A Practical Guide with Examples



Design patterns are proven solutions to typical development problems. In Rust, due to its unique ownership system and memory model, the implementation of classic patterns often differs from languages with garbage collection. In this article, we will cover three key patterns: Singleton, Builder, and Strategy, adapted to Rust's features.



1. Singleton Pattern in Rust: Global State Without unsafe



Singleton guarantees that a class has only one instance and provides a global access point to it. In Rust, the once_cell crate or the stable std::sync::OnceLock (since Rust 1.70) is ideal for this.



Implementation with OnceLock

use std::sync::OnceLock;

pub struct Logger { level: String,}

impl Logger { pub fn global() -> &'static Logger { static INSTANCE: OnceLock<Logger> = OnceLock::new(); INSTANCE.get_or_init(|| Logger { level: "info".to_string(), }) }

pub fn log(&self, msg: &str) { println!("[{}] {}", self.level, msg); }}

fn main() { let logger = Logger::global(); logger.log("Hello, Rust!"); // Logger::global() will return the same instance}


Advantages: Safety (no unsafe), lazy initialization, thread safety. Disadvantage — global state complicates testing.



2. Builder Pattern: Constructing Complex Objects



Builder separates the construction of a complex object from its representation. In Rust, it is especially useful for structs with many optional fields.



Example: HTTP Client Configuration

pub struct HttpClient {    url: String,    timeout: u64,    headers: Vec<(String, String)>,}

pub struct HttpClientBuilder { url: String, timeout: Option<u64>, headers: Vec<(String, String)>,}

impl HttpClientBuilder { pub fn new(url: &str) -> Self { HttpClientBuilder { url: url.to_string(), timeout: None, headers: vec![], } }

pub fn timeout(mut self, secs: u64) -> Self { self.timeout = Some(secs); self }

pub fn header(mut self, key: &str, value: &str) -> Self { self.headers.push((key.to_string(), value.to_string())); self }

pub fn build(self) -> HttpClient { HttpClient { url: self.url, timeout: self.timeout.unwrap_or(30), headers: self.headers, } }}

fn main() { let client = HttpClientBuilder::new("https://api.example.com") .timeout(10) .header("Authorization", "Bearer token123") .build(); println!("Client URL: {}", client.url);}


Important: Using self (not &self) in builder methods avoids cloning and makes the API more ergonomic.



3. Strategy Pattern: Algorithms as Interchangeable Strategies



Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable. In Rust, this is implemented via traits and dynamic dispatch (Box<dyn Trait>).



Example: Sorting System

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

struct BubbleSort;impl SortStrategy for BubbleSort { fn sort(&self, data: &mut [i32]) { 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, da ```

Blogs

Book Recommendations