Design Patterns in Rust with Examples: From A to Z

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

Design Patterns in Rust: From Theory to Practice



Design patterns are proven solutions to common problems in software development. In Rust, due to its unique ownership system and the absence of classical OOP, many classic patterns take on a new meaning. In this article, we will cover four key patterns: Singleton, Factory, Builder, and Strategy, adapted for idiomatic Rust.



1. Singleton in Rust



Rust does not have global static variables in the conventional sense. However, a Singleton can be implemented using lazy_static or OnceCell. Let's look at an example with std::sync::OnceLock (available since Rust 1.70).



Implementation with OnceLock



use std::sync::OnceLock;

static CONFIG: OnceLock<Config> = OnceLock::new();

#[derive(Debug)]struct Config { db_url: String, port: u16,}

fn get_config() -> &'static Config { CONFIG.get_or_init(|| { Config { db_url: String::from("postgres://localhost"), port: 5432, } })}

fn main() { let cfg = get_config(); println!("{:?}", cfg);}


Key point: OnceLock guarantees single initialization and thread safety. This is an idiomatic approach for Rust.



2. Factory Pattern via Traits



The Factory pattern in Rust is perfectly implemented using traits. We don't need abstract classes — it's enough to define an interface through a trait.



Example: creating vehicles



trait Transport {    fn deliver(&self);}

struct Truck;impl Transport for Truck { fn deliver(&self) { println!("Delivery by truck on land"); }}

struct Ship;impl Transport for Ship { fn deliver(&self) { println!("Delivery by ship by sea"); }}

struct LogisticsFactory;

impl LogisticsFactory { fn create_transport(route_type: &str) -> Box<dyn Transport> { match route_type { "land" => Box::new(Truck), "sea" => Box::new(Ship), _ => panic!("Unknown route type"), } }}

fn main() { let transport = LogisticsFactory::create_transport("sea"); transport.deliver();}


Advantage: Rust does not require inheritance — polymorphism is achieved through traits and Box<dyn Trait>.



3. Builder Pattern with a Typed Chain



The Builder pattern is widely used in Rust, especially for structures with many optional parameters. We implement it using a typed method chain that returns &mut Self.



Example: creating an HTTP request



#[derive(Debug, Default)]struct HttpRequest {    url: String,    method: String,    headers: Vec<(String, String)>,    body: Option<String>,}

struct HttpRequestBuilder { request: HttpRequest,}

impl HttpRequestBuilder { fn new(url: &str) -> Self { HttpRequestBuilder { request: HttpRequest { url: url.to_string(), method: "GET".to_string(), ..Default::default() }, } }

fn method(&mut self, method: &str) -> &mut Self { self.request.method = method.to_string(); self }

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

fn body(&mut self, body: &str) -> &mut Self { self.request.body = Some(body.to_string()); self }

fn build(&self) -> HttpRequest { // Validation can be added here HttpRequest { ..self.request.clone() } }}

fn ma

Blogs

Book Recommendations