Structs

Struct Design Patterns

Idiomatic Rust code uses four core struct design patterns—the Newtype pattern, the Builder pattern, the Typestate pattern, and the RAII Guard pattern—to turn domain rules and resource lifecycles into compile-time guarantees with zero runtime overhead.

Overview of Core Struct Patterns

Pattern Structure Problem Solved
Newtype Single-field tuple struct struct Miles(f64); Prevents mixing up primitive values of the same underlying type (UserId(u64) vs. OrderId(u64)) and circumvents the orphan rule
Builder Helper struct ServerBuilder with chained methods ending in .build() Replaces multi-argument constructors or telescoping new_* functions when many optional fields exist
Typestate Generic phantom marker File<Open> vs. File<Closed> Encodes valid state transitions into the type system so invalid method calls fail at compile time
RAII Guard Struct implementing Drop (e.g., MutexGuard, TempDir) Guarantees deterministic cleanup (unlocking, rolling back, closing) on every return path and panic

Pattern 1: The Newtype Pattern for Type-Safe Domain Primitives

Wrapping a primitive type in a single-field tuple struct (#[repr(transparent)]) has the exact same memory representation and performance as the inner type, while making accidental unit or ID mix-ups a compile error:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct UserId(u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct InvoiceId(u64);

fn fetch_invoice(user: UserId, invoice: InvoiceId) {
    println!("Fetching invoice #{} for user #{}", invoice.0, user.0);
}

fn main() {
    let uid = UserId(1001);
    let iid = InvoiceId(5500);

    fetch_invoice(uid, iid);
    // `fetch_invoice(iid, uid)` would fail at compile time with mismatched types!
}
Fetching invoice #5500 for user #1001

Pattern 2: The Builder Pattern for Complex Configuration

When a struct has required parameters alongside several optional settings, a dedicated builder struct collects configuration step-by-step and validates or constructs the target struct in .build():

#[derive(Debug)]
pub struct ConnectionPool {
    url: String,
    max_connections: usize,
    idle_timeout_secs: u64,
}

pub struct ConnectionPoolBuilder {
    url: String,
    max_connections: usize,
    idle_timeout_secs: u64,
}

impl ConnectionPoolBuilder {
    pub fn new(url: impl Into<String>) -> Self {
        Self {
            url: url.into(),
            max_connections: 10,
            idle_timeout_secs: 60,
        }
    }

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

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

    pub fn build(self) -> Result<ConnectionPool, &'static str> {
        if self.max_connections == 0 {
            return Err("max_connections must be at least 1");
        }
        Ok(ConnectionPool {
            url: self.url,
            max_connections: self.max_connections,
            idle_timeout_secs: self.idle_timeout_secs,
        })
    }
}

fn main() {
    let pool = ConnectionPoolBuilder::new("postgres://localhost/app")
        .max_connections(32)
        .idle_timeout_secs(120)
        .build()
        .unwrap();

    println!("{pool:?} (url = {})", pool.url);
}
ConnectionPool { url: "postgres://localhost/app", max_connections: 32, idle_timeout_secs: 120 } (url = postgres://localhost/app)

Pattern 3: The RAII Scope Guard Pattern

By tying resource acquisition to struct initialization and resource release to Drop::drop(&mut self), a guard struct ensures cleanup always runs when the scope exits:

struct TimerGuard {
    operation: &'static str,
}

impl TimerGuard {
    fn start(operation: &'static str) -> Self {
        println!("[START] {}", operation);
        Self { operation }
    }
}

impl Drop for TimerGuard {
    fn drop(&mut self) {
        println!("[FINISH] {}", self.operation);
    }
}

fn main() {
    {
        let _guard = TimerGuard::start("Database Migration");
        println!("  Running migration steps...");
    } // `_guard` is automatically dropped here!
}
[START] Database Migration
  Running migration steps...
[FINISH] Database Migration