Production-ready Rust patterns for type-safe domain modeling including newtypes, type states, builders, smart constructors, and const generics. Use when creating domain primitives (IDs, emails), state machines, builders, or enforcing compile-time invariants in Rust code.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
name: rust-core-patterns description: Production-ready Rust patterns for type-safe domain modeling including newtypes, type states, builders, smart constructors, and const generics. Use when creating domain primitives (IDs, emails), state machines, builders, or enforcing compile-time invariants in Rust code.
Rust Core Patterns
Minimal, production-ready Rust patterns for memory safety and type-driven design
Version Context
- Rust: 1.91.1 (stable)
- Edition: 2021
- MSRV: 1.78+
When to Use This Skill
- Creating domain primitives (UserId, Email, OrderId)
- Implementing state machines with compile-time guarantees
- Building complex objects with mandatory fields
- Enforcing invariants at construction time
- Fixed-size constraints known at compile time
- Testable dependency injection patterns
Core Pattern Library
1. Newtype Pattern for Domain Modeling
use std::fmt;
/// UserId newtype with validation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UserId(uuid::Uuid);
impl UserId {
pub fn new() -> Self {
Self(uuid::Uuid::new_v4())
}
pub fn from_str(s: &str) -> Result<Self, ParseError> {
uuid::Uuid::parse_str(s)
.map(Self)
.map_err(|_| ParseError::InvalidUserId)
}
pub fn as_uuid(&self) -> &uuid::Uuid {
&self.0
}
}
impl fmt::Display for UserId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Email newtype with compile-time validation
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Email(String);
impl Email {
pub fn new(s: String) -> Result<Self, ValidationError> {
if s.contains('@') && s.len() >= 3 {
Ok(Self(s))
} else {
Err(ValidationError::InvalidEmail)
}
}
pub fn as_str(&self) -> &str {
&self.0
}
}
2. Type State Pattern
use std::marker::PhantomData;
/// Connection lifecycle using type states
pub struct Connection<State> {
inner: ConnectionInner,
_state: PhantomData<State>,
}
pub struct Disconnected;
pub struct Connected;
pub struct Authenticated;
impl Connection<Disconnected> {
pub fn new() -> Self {
Self {
inner: ConnectionInner::new(),
_state: PhantomData,
}
}
pub async fn connect(self) -> Result<Connection<Connected>, ConnectionError> {
self.inner.connect().await?;
Ok(Connection {
inner: self.inner,
_state: PhantomData,
})
}
}
impl Connection<Connected> {
pub async fn authenticate(
self,
credentials: Credentials,
) -> Result<Connection<Authenticated>, AuthError> {
self.inner.auth(credentials).await?;
Ok(Connection {
inner: self.inner,
_state: PhantomData,
})
}
}
impl Connection<Authenticated> {
pub async fn query(&self, sql: &str) -> Result<QueryResult, QueryError> {
self.inner.execute(sql).await
}
}
3. Builder Pattern with Typestate
/// Builder with mandatory fields enforced at compile time
#[derive(Default)]
pub struct RequestBuilder<Email, Name> {
email: Email,
name: Name,
age: Option<u8>,
}
pub struct Set<T>(T);
pub struct Unset;
impl RequestBuilder<Unset, Unset> {
pub fn new() -> Self {
Self::default()
}
}
impl<N> RequestBuilder<Unset, N> {
pub fn email(self, email: String) -> RequestBuilder<Set<String>, N> {
RequestBuilder {
email: Set(email),
name: self.name,
age: self.age,
}
}
}
impl<E> RequestBuilder<E, Unset> {
pub fn name(self, name: String) -> RequestBuilder<E, Set<String>> {
RequestBuilder {
email: self.email,
name: Set(name),
age: self.age,
}
}
}
impl RequestBuilder<Set<String>, Set<String>> {
pub fn age(mut self, age: u8) -> Self {
self.age = Some(age);
self
}
pub fn build(self) -> CreateUserRequest {
CreateUserRequest {
email: self.email.0,
name: self.name.0,
age: self.age,
}
}
}
4. Smart Constructors
/// NonEmptyVec ensures the vector is never empty
#[derive(Debug, Clone)]
pub struct NonEmptyVec<T> {
head: T,
tail: Vec<T>,
}
impl<T> NonEmptyVec<T> {
/// Smart constructor enforces non-empty invariant
pub fn new(head: T, tail: Vec<T>) -> Self {
Self { head, tail }
}
pub fn from_vec(mut vec: Vec<T>) -> Option<Self> {
if vec.is_empty() {
None
} else {
let head = vec.remove(0);
Some(Self { head, tail: vec })
}
}
pub fn head(&self) -> &T {
&self.head
}
pub fn len(&self) -> usize {
1 + self.tail.len()
}
}
5. Const Generics for Compile-Time Bounds
/// String with compile-time length bounds
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoundedString<const MIN: usize, const MAX: usize> {
value: String,
}
impl<const MIN: usize, const MAX: usize> BoundedString<MIN, MAX> {
pub fn new(value: String) -> Result<Self, BoundsError> {
let len = value.len();
if len < MIN {
Err(BoundsError::TooShort { min: MIN, actual: len })
} else if len > MAX {
Err(BoundsError::TooLong { max: MAX, actual: len })
} else {
Ok(Self { value })
}
}
pub fn as_str(&self) -> &str {
&self.value
}
}
/// Type aliases for domain constraints
pub type Username = BoundedString<3, 20>;
pub type Bio = BoundedString<0, 500>;
6. Safe Concurrency Primitives
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::{RwLock, Semaphore};
/// Thread-safe counter using atomics
pub struct Counter {
value: AtomicU64,
}
impl Counter {
pub fn new() -> Self {
Self {
value: AtomicU64::new(0),
}
}
pub fn increment(&self) -> u64 {
self.value.fetch_add(1, Ordering::SeqCst)
}
pub fn get(&self) -> u64 {
self.value.load(Ordering::SeqCst)
}
}
/// Bounded concurrency with semaphore
pub struct ConcurrencyLimiter {
semaphore: Arc<Semaphore>,
}
impl ConcurrencyLimiter {
pub fn new(max_concurrent: usize) -> Self {
Self {
semaphore: Arc::new(Semaphore::new(max_concurrent)),
}
}
pub async fn run<F, T>(&self, f: F) -> T
where
F: std::future::Future<Output = T>,
{
let _permit = self.semaphore.acquire().await.unwrap();
f.await
}
}
7. Trait-Based Dependency Injection
use async_trait::async_trait;
/// Repository trait for abstraction
#[async_trait]
pub trait UserRepository: Send + Sync {
async fn find_by_id(&self, id: UserId) -> Result<User, RepoError>;
async fn save(&self, user: &User) -> Result<(), RepoError>;
}
/// Service depends on trait, not concrete type
pub struct UserService<R: UserRepository> {
repo: Arc<R>,
}
impl<R: UserRepository> UserService<R> {
pub fn new(repo: Arc<R>) -> Self {
Self { repo }
}
pub async fn get_user(&self, id: UserId) -> Result<User, ServiceError> {
self.repo.find_by_id(id)
.await
.map_err(ServiceError::from)
}
}
Key Principles
- Make invalid states unrepresentable - Use types to enforce invariants
- Fail fast at construction - Validate in constructors, not at use sites
- Prefer compile-time over runtime - Use const generics and type states
- Explicit over implicit - No hidden state or magic
- Zero-cost abstractions - Patterns compile to optimal code
Performance Notes
- Newtypes: Zero runtime cost (optimized away)
- Type states: Zero runtime cost (PhantomData is zero-sized)
- Const generics: Compile-time validation, no runtime checks
- Arc for shared ownership: Minimal overhead, lock-free reference counting
Pattern Selection Guide
- Newtypes: Always for domain primitives (IDs, emails, etc.)
- Type states: Complex state machines, connection lifecycles
- Builders: Objects with many optional fields or configuration
- Smart constructors: When invariants must be maintained
- Const generics: Fixed-size constraints known at compile time
- Trait injection: Testing, swappable implementations
More by matthewharwood
View allUtopia.fyi fluid typography and spacing system using clamp(), modular scales, and proportional design. Use when implementing fluid type scales, space palettes, t-shirt sizing, space pairs, or creating fluid responsive designs without breakpoints. Foundation for systematic, proportional scaling across all viewports.
Production observability with tracing, OpenTelemetry, and Prometheus metrics including structured logging, instrumented functions, distributed tracing, health checks, and request correlation. Use when adding logging, metrics, tracing, or health endpoints to Rust services.
CSS Grid, Flexbox, and Subgrid using Utopia.fyi fluid spacing for gaps and gutters. Use when creating two-dimensional grid layouts, one-dimensional flex layouts, nested subgrids, or implementing the Utopian declarative grid system. Combines layout primitives with fluid space scales for proportional, responsive layouts without breakpoints.
Service architecture patterns for Axum applications including layered design (Router → Handler → Service → Repository), AppState with FromRef for dependency injection, Tower ServiceBuilder for middleware composition, and modular router organization. Use when designing service layers, managing dependencies, composing middleware stacks, or structuring Axum applications.
