Advanced Rust Design Patterns

Builder Pattern

Because Rust intentionally omits function overloading, default function arguments, and named keyword arguments, the Builder pattern is the standard idiomatic way to construct complex structs that have a mix of required parameters and optional configuration fields.

Two Idiomatic Builder Variants in Rust:

  1. Owned / Consuming Builder (`fn timeout(mut self, d: Duration) -> Self`):
     - Zero clones in `.build(self)`!
     - Works cleanly with single-expression chaining (`Builder::new()...build()`)

  2. Mutable Reference Builder (`fn arg(&mut self, a: &str) -> &mut Self`):
     - Used by `std::process::Command` & `std::fs::OpenOptions`
     - Supports conditional `if` branches and loops without reassigning `let mut b = b.arg(...)`!

Comparing Consuming (mut self -> Self) vs Borrowed (&mut self -> &mut Self) Builders

Aspect Consuming Builder (mut self -> Self) Mutable Reference Builder (&mut self -> &mut Self)
Chaining Syntax ServerBuilder::new(8080).workers(4).build() Command::new("ls").arg("-l").spawn()
Loops & Conditional Branches Requires b = b.header(k, v); reassignment Natural if flag { b.arg("-v"); } without reassignment
Reusing Builder for Multiple Instances Requires .clone() on the builder .build(&self) or .spawn(&mut self) can be called repeatedly!
Zero-Copy Ownership Transfer in .build() Yes (self moves fields directly into target struct!) Requires cloning owned fields (or std::mem::take)

Implementing a Consuming Builder with Validation in .build()

Pass required fields that have no sensible default directly into Builder::new(...), provide fluent setter methods for optional fields, and validate cross-field invariants inside .build() returning Result<Target, BuildError>:

use std::time::Duration;

#[derive(Debug, PartialEq, Eq)]
pub enum ConfigError {
    ZeroMaxConnections,
    IdleTimeoutExceedsMaxLifetime,
}

#[derive(Debug, PartialEq, Eq)]
pub struct PoolConfig {
    pub endpoint: String,
    pub max_connections: u32,
    pub idle_timeout: Duration,
    pub max_lifetime: Duration,
}

#[derive(Debug, Clone)]
pub struct PoolConfigBuilder {
    endpoint: String,
    max_connections: u32,
    idle_timeout: Duration,
    max_lifetime: Duration,
}

impl PoolConfigBuilder {
    /// Required parameter `endpoint` is passed upfront; optional fields get sensible defaults.
    pub fn new(endpoint: impl Into<String>) -> Self {
        Self {
            endpoint: endpoint.into(),
            max_connections: 16,
            idle_timeout: Duration::from_secs(60),
            max_lifetime: Duration::from_secs(1800),
        }
    }

    pub fn max_connections(mut self, count: u32) -> Self {
        self.max_connections = count;
        self
    }

    pub fn idle_timeout(mut self, timeout: Duration) -> Self {
        self.idle_timeout = timeout;
        self
    }

    pub fn max_lifetime(mut self, lifetime: Duration) -> Self {
        self.max_lifetime = lifetime;
        self
    }

    /// Consumes the builder, validates cross-field invariants, and moves `self.endpoint` with 0 clones!
    pub fn build(self) -> Result<PoolConfig, ConfigError> {
        if self.max_connections == 0 {
            return Err(ConfigError::ZeroMaxConnections);
        }
        if self.idle_timeout > self.max_lifetime {
            return Err(ConfigError::IdleTimeoutExceedsMaxLifetime);
        }
        Ok(PoolConfig {
            endpoint: self.endpoint,
            max_connections: self.max_connections,
            idle_timeout: self.idle_timeout,
            max_lifetime: self.max_lifetime,
        })
    }
}

fn main() {
    let config = PoolConfigBuilder::new("postgres://localhost:5432/app")
        .max_connections(32)
        .idle_timeout(Duration::from_secs(120))
        .build()
        .unwrap();

    assert_eq!(config.max_connections, 32);

    // Invalid cross-field configuration is rejected cleanly by `.build()`:
    let invalid = PoolConfigBuilder::new("postgres://localhost:5432/app")
        .idle_timeout(Duration::from_secs(3600))
        .max_lifetime(Duration::from_secs(600))
        .build();
    assert_eq!(invalid, Err(ConfigError::IdleTimeoutExceedsMaxLifetime));

    println!("Built validated PoolConfig: max_connections={}", config.max_connections);
}
Built validated PoolConfig: max_connections=32