Advanced Rust Design Patterns

Newtype Pattern

The Newtype pattern wraps an existing type inside a single-field tuple struct (pub struct UserId(u64); or #[repr(transparent)] pub struct Meters(pub f64);) to create a distinct, compiler-enforced type at compile time with zero memory or CPU overhead at runtime.

Without Newtypes (Primitive Obsession Bug!):
  `fn transfer(from_account: u64, to_account: u64, amount_cents: u64)`
  Caller writes: `transfer(amount, from_id, to_id)`  ---> Compiles silently! Catastrophic bug!

With Newtypes (Compile-Time Domain Safety + Zero Runtime Cost):
  `fn transfer(from: AccountId, to: AccountId, amount: Cents)`
  Caller writes: `transfer(amount, from_id, to_id)`  ---> Compile Error: expected `AccountId`, found `Cents`!

Four Core Uses of the Newtype Pattern in Rust

Use Case Example Why a Type Alias (type Foo = u64;) Is Not Enough
1. Preventing Unit / ID Mix-Ups struct UserId(u64) vs struct OrderId(u64) A type alias is just a synonym—rustc allows passing OrderId where UserId is expected if they are type aliases!
2. Enforcing Construction Invariants ("Parse, Don't Validate") struct NonEmptyStr(String) with private field .0 Guarantees that any NonEmptyStr instance in the program was validated at construction time (try_new).
3. Bypassing the Orphan Rule struct PrettyVec<T>(pub Vec<T>) implementing fmt::Display Rust's coherence rules forbid implementing foreign trait Display on foreign type Vec<T> directly; wrapping it in a local newtype makes the type local!
4. Hiding / Restricting Inner Trait Impls struct SecretToken(String) omitting Display and redacting Debug Prevents accidental logging of sensitive credentials while still holding a String internally.

Enforcing Domain Invariants ("Parse, Don't Validate") and Bypassing the Orphan Rule

By keeping the inner tuple field private (struct EmailAddress(String); without pub on String), external modules can only obtain an EmailAddress through your validated EmailAddress::parse(...) constructor. Any downstream function accepting email: &EmailAddress never needs to re-check whether the string contains '@'!

use std::fmt;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EmailAddress(String); // Private inner field enforces invariant!

#[derive(Debug, PartialEq, Eq)]
pub enum EmailError {
    MissingAtSymbol,
    EmptyLocalOrDomain,
}

impl EmailAddress {
    pub fn parse(raw: impl Into<String>) -> Result<Self, EmailError> {
        let s = raw.into();
        let (local, domain) = s.split_once('@').ok_or(EmailError::MissingAtSymbol)?;
        if local.is_empty() || domain.is_empty() || !domain.contains('.') {
            return Err(EmailError::EmptyLocalOrDomain);
        }
        Ok(Self(s))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

/// Implementing foreign trait `fmt::Display` on a newtype wrapping foreign type `Vec<T>`
/// (which would violate the Orphan Rule if implemented on `Vec<T>` directly!):
#[repr(transparent)]
pub struct CsvRow<T>(pub Vec<T>);

impl<T: fmt::Display> fmt::Display for CsvRow<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (i, item) in self.0.iter().enumerate() {
            if i > 0 {
                f.write_str(", ")?;
            }
            write!(f, "{item}")?;
        }
        Ok(())
    }
}

fn main() {
    let valid = EmailAddress::parse("ada@example.org").unwrap();
    assert_eq!(valid.as_str(), "ada@example.org");
    assert_eq!(
        EmailAddress::parse("invalid-email"),
        Err(EmailError::MissingAtSymbol)
    );

    let row = CsvRow(vec![10, 20, 30, 40]);
    println!("Validated email: {}, formatted CsvRow: [{}]", valid.as_str(), row);
}
Validated email: ada@example.org, formatted CsvRow: [10, 20, 30, 40]

Selective Trait Forwarding vs Deref

Avoid implementing Deref<Target = Inner> on a domain newtype like UserId(u64) or Meters(f64) if the whole point of the newtype is to restrict operations (for example, multiplying UserId * UserId makes no sense). Instead, derive only the specific traits that make domain sense (#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]) and implement AsRef<str> or explicit accessor methods (.get(), .as_str()).