| name | rust-design-patterns |
| description | | Use when this capability is needed. |
Rust Design Patterns
Collection of idiomatic patterns, architectural choices, and best practices for Rust.
Quick Navigation
- references/creational.md - Builder, Factory, Singleton patterns
- references/structural.md - Newtype, Wrapper, Decorator patterns
- references/behavioral.md - State machines, Visitor, Strategy
Idioms
Default Constructors
#[derive(Default)]
struct Config {
timeout: u64,
retries: u32,
}
impl Default for Config {
fn default() -> Self {
Self {
timeout: 30,
retries: 3,
}
}
}
let config = Config::default();
let config = Config { retries: 5, ..Default::default() };
Constructor Functions
impl User {
pub fn new(name: String, email: String) -> Self {
Self { id: Uuid::new_v4(), name, email, created_at: Utc::now() }
}
pub fn anonymous() -> Self {
Self::new("Anonymous".into(), "anonymous@example.com".into())
}
pub fn try_new(name: &str, email: &str) -> Result<Self, ValidationError> {
validate_email(email)?;
Ok(Self::new(name.into(), email.into()))
}
}
Destructuring and Ignore
let Point { x, y: _ } = point;
let Point { x, .. } = point;
match result {
Ok(value) if value > 0 => handle_positive(value),
Ok(_) => handle_zero_or_negative(),
Err(e) => handle_error(e),
}
Creational Patterns
Builder Pattern
For complex object construction:
pub struct ServerConfig {
host: String,
port: u16,
workers: usize,
tls: Option<TlsConfig>,
}
#[derive(Default)]
pub struct ServerConfigBuilder {
host: Option<String>,
port: Option<u16>,
workers: Option<usize>,
tls: Option<TlsConfig>,
}
impl ServerConfigBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = Some(host.into());
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
( , workers: ) {
.workers = (workers);
}
( , tls: TlsConfig) {
.tls = (tls);
}
() <ServerConfig, BuildError> {
(ServerConfig {
host: .host.(|| .()),
port: .port.(BuildError::MissingPort)?,
workers: .workers.(num_cpus::()),
tls: .tls,
})
}
}
= ServerConfigBuilder::()
.()
.()
.()
.()?;
Typestate Builder
Compile-time enforcement of required fields:
struct NoHost;
struct HasHost(String);
struct NoPort;
struct HasPort(u16);
struct Builder<H, P> {
host: H,
port: P,
workers: usize,
}
impl Builder<NoHost, NoPort> {
pub fn new() -> Self {
Self { host: NoHost, port: NoPort, workers: 4 }
}
}
impl<P> Builder<NoHost, P> {
pub fn host(self, host: impl Into<String>) -> Builder<HasHost, P> {
Builder {
host: HasHost(host.into()),
port: self.port,
workers: self.workers,
}
}
}
impl<H> Builder<H, NoPort> {
pub fn port(self, port: u16) -> Builder<H, HasPort> {
Builder {
host: self.host,
port: HasPort(port),
workers: self.workers,
}
}
}
<HasHost, HasPort> {
() Server {
Server {
host: .host.,
port: .port.,
workers: .workers,
}
}
}
= Builder::()
.()
.()
.();
Structural Patterns
Newtype Pattern
Type safety through wrapper types:
struct UserId(u64);
struct OrderId(u64);
fn get_user_orders(user_id: UserId) -> Vec<OrderId> { ... }
pub struct Email(String);
impl Email {
pub fn new(s: impl AsRef<str>) -> Result<Self, EmailError> {
let s = s.as_ref();
if Self::is_valid(s) {
Ok(Self(s.to_string()))
} else {
Err(EmailError::Invalid)
}
}
fn is_valid(s: &str) -> bool {
s.contains('@') && s.contains('.')
}
pub fn as_str(&) & {
&.
}
}
std::ops::Deref;
{
= ;
(&) & { &. }
}
Extension Traits
Add methods to external types:
pub trait StringExt {
fn is_blank(&self) -> bool;
fn truncate_with_ellipsis(&self, max_len: usize) -> String;
}
impl StringExt for str {
fn is_blank(&self) -> bool {
self.trim().is_empty()
}
fn truncate_with_ellipsis(&self, max_len: usize) -> String {
if self.len() <= max_len {
self.to_string()
} else {
format!("{}...", &self[..max_len.saturating_sub(3)])
}
}
}
" ".is_blank();
"Hello World".truncate_with_ellipsis(8);
RAII Guards
Automatic cleanup via Drop:
pub struct TempFile {
path: PathBuf,
}
impl TempFile {
pub fn new(name: &str) -> std::io::Result<Self> {
let path = std::env::temp_dir().join(name);
std::fs::File::create(&path)?;
Ok(Self { path })
}
pub fn path(&self) -> &Path {
&self.path
}
}
impl Drop for TempFile {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
}
}
{
let temp = TempFile::new("data.tmp")?;
write_data(temp.path())?;
}
Facade Pattern
Simplify complex subsystems:
pub struct Database {
pool: ConnectionPool,
cache: Cache,
metrics: Metrics,
}
impl Database {
pub fn new(config: &Config) -> Result<Self, DbError> {
Ok(Self {
pool: ConnectionPool::new(&config.db_url)?,
cache: Cache::new(config.cache_size),
metrics: Metrics::new(),
})
}
pub async fn get_user(&self, id: UserId) -> Result<User, DbError> {
if let Some(user) = self.cache.get(&id) {
self.metrics.record_cache_hit();
return Ok(user);
}
let conn = self.pool.get().await?;
let user = conn.query_one("SELECT * FROM users WHERE id = $1", &[&id]).await?;
.cache.(id, user.());
.metrics.();
(user)
}
}
Behavioral Patterns
State Machine with Enums
pub enum ConnectionState {
Disconnected,
Connecting { attempt: u32, started_at: Instant },
Connected { session: Session },
Disconnecting,
}
pub struct Connection {
state: ConnectionState,
config: Config,
}
impl Connection {
pub fn connect(&mut self) -> Result<(), ConnectionError> {
self.state = match std::mem::take(&mut self.state) {
ConnectionState::Disconnected => {
ConnectionState::Connecting {
attempt: 1,
started_at: Instant::now(),
}
}
other => return Err(ConnectionError::InvalidState),
};
Ok(())
}
pub fn on_connected(&mut self, session: Session) {
if matches!(self.state, ConnectionState::Connecting { .. }) {
self.state = ConnectionState::Connected { session };
}
}
pub fn is_connected(&self) -> bool {
matches!(self.state, ConnectionState::Connected { .. })
}
}
impl Default {
() {
::Disconnected
}
}
Typestate Pattern
Encode states in the type system:
struct Draft;
struct Published;
struct Archived;
struct Post<State> {
content: String,
_state: std::marker::PhantomData<State>,
}
impl Post<Draft> {
pub fn new(content: String) -> Self {
Self { content, _state: PhantomData }
}
pub fn edit(&mut self, content: String) {
self.content = content;
}
pub fn publish(self) -> Post<Published> {
Post { content: self.content, _state: PhantomData }
}
}
impl Post<Published> {
pub fn archive(self) -> Post<Archived> {
Post { content: self.content, _state: PhantomData }
}
}
let post = Post::new("Hello".into());
post.edit(.());
= post.();
Strategy Pattern
trait Compressor {
fn compress(&self, data: &[u8]) -> Vec<u8>;
fn decompress(&self, data: &[u8]) -> Vec<u8>;
}
struct GzipCompressor;
struct LzmaCompressor;
impl Compressor for GzipCompressor {
fn compress(&self, data: &[u8]) -> Vec<u8> { }
fn decompress(&self, data: &[u8]) -> Vec<u8> { }
}
impl Compressor for LzmaCompressor {
fn compress(&self, data: &[u8]) -> Vec<u8> { }
fn decompress(&self, data: &[]) <> { }
}
<C: Compressor> {
compressor: C,
}
<C: Compressor> FileProcessor<C> {
(compressor: C) {
{ compressor }
}
(&, data: &[]) <> {
.compressor.(data)
}
}
= FileProcessor::(GzipCompressor);
= processor.(&data);
Command Pattern
trait Command {
fn execute(&self);
fn undo(&self);
}
struct InsertText {
position: usize,
text: String,
}
impl Command for InsertText {
fn execute(&self) {
}
fn undo(&self) {
}
}
struct CommandHistory {
commands: Vec<Box<dyn Command>>,
current: usize,
}
impl CommandHistory {
pub fn execute(&mut self, cmd: Box<dyn Command>) {
cmd.execute();
self.commands.truncate(self.current);
self.commands.push(cmd);
self.current += 1;
}
pub fn undo(&mut ) {
.current > {
.current -= ;
.commands[.current].();
}
}
(& ) {
.current < .commands.() {
.commands[.current].();
.current += ;
}
}
}
Anti-Patterns to Avoid
God Objects
struct App {
users: Vec<User>,
orders: Vec<Order>,
products: Vec<Product>,
fn do_everything(&mut self) { ... }
}
struct UserService { ... }
struct OrderService { ... }
struct ProductService { ... }
struct App {
users: UserService,
orders: OrderService,
products: ProductService,
}
Stringly Typed
fn set_status(status: &str) {
match status {
"pending" | "active" | "done" => { ... }
_ => panic!("Invalid status"),
}
}
enum Status { Pending, Active, Done }
fn set_status(status: Status) {
match status {
Status::Pending => { ... }
Status::Active => { ... }
Status::Done => { ... }
}
}
Deref Polymorphism
struct Dog {
animal: Animal,
}
impl Deref for Dog {
type Target = Animal;
fn deref(&self) -> &Animal { &self.animal }
}
trait Animal {
fn speak(&self);
}
impl Animal for Dog {
fn speak(&self) { println!("Woof!"); }
}
Pattern Selection Guide
| Need | Pattern |
|---|
| Complex construction | Builder |
| Compile-time construction validation | Typestate Builder |
| Type safety for primitives | Newtype |
| Add methods to foreign types | Extension Trait |
| Automatic cleanup | RAII Guard |
| Multiple states with transitions | State Machine |
| Compile-time state validation | Typestate |
| Swappable algorithms | Strategy |
| Undoable operations | Command |
Pattern Checklist
- Name the problem before choosing a pattern.
- Prefer plain functions and enums before trait-object abstractions.
- Use builders when construction has optional fields, validation, or growth pressure.
- Use typestate only when compile-time state prevents real misuse.
- Keep RAII guards small and obvious; document side effects in
Drop.
- Test pattern examples for both valid paths and misuse they are meant to prevent.
References
Source: adxptived/Rust-Skills — distributed by TomeVault.