Advanced Design Patterns in Rust: 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

Design Patterns in Rust: Advanced Level with Examples

\n\n

Design patterns are proven solutions to typical problems that help create flexible, maintainable, and scalable architecture. In Rust, with its ownership, borrowing, and trait system, many classic patterns take on a unique implementation. In this article, we will examine advanced design patterns that are particularly effective in the Rust ecosystem.

\n\n

1. Advanced Builder

\n\n

The classic Builder pattern in Rust is often implemented through a chain of methods returning &mut Self. However, at an advanced level, we use a typed Builder with state types to ensure correctness at compile time.

\n\n

Problem: controlling mandatory fields

\n

A regular Builder does not guarantee that the user has filled in all mandatory fields. We might forget to call .build() or fail to set a critically important parameter.

\n\n

Solution: Builder with phantom types

\n
use std::marker::PhantomData;\n\n// State types for fields\nstruct UsernameNotSet;\nstruct UsernameSet;\nstruct PasswordNotSet;\nstruct PasswordSet;\n\nstruct UserBuilder<U, P> {\n    username: Option<String>,\n    password: Option<String>,\n    _username: PhantomData<U>,\n    _password: PhantomData<P>,\n}\n\nimpl UserBuilder<UsernameNotSet, PasswordNotSet> {\n    pub fn new() -> Self {\n        Self {\n            username: None,\n            password: None,\n            _username: PhantomData,\n            _password: PhantomData,\n        }\n    }\n}\n\nimpl<P> UserBuilder<UsernameNotSet, P> {\n    pub fn username(mut self, name: &str) -> UserBuilder<UsernameSet, P> {\n        self.username = Some(name.to_string());\n        UserBuilder {\n            username: self.username,\n            password: self.password,\n            _username: PhantomData,\n            _password: PhantomData,\n        }\n    }\n}\n\nimpl<U> UserBuilder<U, PasswordNotSet> {\n    pub fn password(mut self, pwd: &str) -> UserBuilder<U, PasswordSet> {\n        self.password = Some(pwd.to_string());\n        UserBuilder {\n            username: self.username,\n            password: self.password,\n            _username: PhantomData,\n            _password: PhantomData,\n        }\n    }\n}\n\nimpl UserBuilder<UsernameSet, PasswordSet> {\n    pub fn build(self) -> User {\n        User {\n            username: self.username.expect("Username must be set"),\n            password: self.password.expect("Password must be set"),\n        }\n    }\n}\n\npub struct User {\n    pub username: String,\n    pub password: String,\n}\n\nfn main() {\n    // Now the compiler will not allow calling build() without setting both fields\n    let user = UserBuilder::new()\n        .username("alice")\n        .password("secret123")\n        .build();\n    println!("User: {}", user.username);\n}
\n\n

Advantage: the "forgot field" error is detected at compile time, not at runtime.

\n\n

2. State Pattern via Traits

\n\n

The State pattern allows an object to change its behavior when its internal state changes. In Rust, this is elegantly implemented using enums and traits.

\n\n

Implementation with enum

\n
enum ConnectionState {\n    Disconnected,\n    Connecting,\n    Connected,\n}\n\nstruct Connection {\n    state: ConnectionState,\n}\n\nimpl Connection {\n    pub fn new() -> Self {\n        Self { state: ConnectionState::Disconnected }\n    }\n\n    pub fn connect(&mut self) {\n        match self.state {\n            ConnectionState::Disconnected => {\n                println!("Starting connection...");\n                self.state = ConnectionState::Connecting;\n            }\n            ConnectionState::Connecting => {\n                printl                

Blogs

Book Recommendations