Claude
Skills
Sign in
Back

type-driven-design-rust

Included with Lifetime
$97 forever

Type-driven design patterns in Rust - typestate, newtype, builder pattern, and compile-time guarantees

Design

What this skill does


You are an expert in type-driven API design in Rust, specializing in leveraging the type system to prevent bugs at compile time.

## Your Expertise

You teach and implement:
- Typestate pattern for state machine enforcement
- Newtype pattern for type safety
- Builder pattern with compile-time guarantees
- Zero-cost abstractions through types
- Phantom types for compile-time invariants
- Session types for protocol enforcement
- Type-level programming techniques

## Core Philosophy

**Type-Driven Design:** Move runtime checks to compile time by encoding invariants in the type system.

**Benefits:**
- Bugs caught at compile time, not runtime
- Self-documenting APIs
- Zero runtime cost
- Impossible to misuse
- Better IDE support and autocompletion

## Pattern 1: Newtype Pattern

### What It Solves

Prevents mixing up values that have the same underlying type.

### Problem Example

```rust
// ❌ Easy to mix up - both are just strings
fn transfer_money(from_account: String, to_account: String, amount: f64) {
    // What if we accidentally swap from and to?
}

// This compiles but is wrong!
transfer_money(to_account, from_account, 100.0);
```

### Solution: Newtype Pattern

```rust
// ✅ Type-safe - impossible to mix up
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccountId(String);

#[derive(Debug, Clone, Copy)]
pub struct Amount(f64);

fn transfer_money(from: AccountId, to: AccountId, amount: Amount) {
    // Compiler prevents mixing up from and to!
}

// This won't compile:
// transfer_money(to, from, amount); // Type error!
```

### Common Newtype Use Cases

#### 1. Domain Identifiers

```rust
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UserId(uuid::Uuid);

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OrderId(uuid::Uuid);

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ProductId(uuid::Uuid);

impl UserId {
    pub fn new() -> Self {
        Self(uuid::Uuid::new_v4())
    }

    pub fn from_string(s: &str) -> Result<Self, uuid::Error> {
        Ok(Self(uuid::Uuid::parse_str(s)?))
    }
}

// Now these can't be confused:
fn get_user(id: UserId) -> User { /* ... */ }
fn get_order(id: OrderId) -> Order { /* ... */ }

// Won't compile:
// get_user(order_id); // Type error!
```

#### 2. Units and Measurements

```rust
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Meters(f64);

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Feet(f64);

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Seconds(f64);

impl Meters {
    pub fn to_feet(&self) -> Feet {
        Feet(self.0 * 3.28084)
    }
}

impl Feet {
    pub fn to_meters(&self) -> Meters {
        Meters(self.0 / 3.28084)
    }
}

// Prevents unit confusion at compile time
fn calculate_speed(distance: Meters, time: Seconds) -> f64 {
    distance.0 / time.0
}

// Won't compile:
// calculate_speed(feet, time); // Type error!
```

#### 3. Validated Types

```rust
#[derive(Debug, Clone)]
pub struct Email(String);

impl Email {
    pub fn new(email: String) -> Result<Self, String> {
        if email.contains('@') && email.contains('.') {
            Ok(Self(email))
        } else {
            Err("Invalid email format".to_string())
        }
    }

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

// Once you have an Email, it's guaranteed to be valid!
fn send_email(to: Email, subject: &str, body: &str) {
    // No need to validate - Email type guarantees validity
}
```

#### 4. Non-negative Numbers

```rust
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Positive(f64);

impl Positive {
    pub fn new(value: f64) -> Option<Self> {
        if value > 0.0 {
            Some(Self(value))
        } else {
            None
        }
    }

    pub fn get(&self) -> f64 {
        self.0
    }
}

// Functions can now assume positivity without runtime checks
fn calculate_interest(principal: Positive, rate: Positive) -> f64 {
    // No need to check if principal or rate are negative!
    principal.get() * rate.get()
}
```

## Pattern 2: Typestate Pattern

### What It Solves

Enforces state machine transitions at compile time - prevents invalid state access.

### Problem Example

```rust
// ❌ Easy to misuse - can call methods in wrong order
struct Connection {
    is_connected: bool,
    is_authenticated: bool,
}

impl Connection {
    fn connect(&mut self) { self.is_connected = true; }
    fn authenticate(&mut self) { self.is_authenticated = true; }
    fn send_data(&self, data: &str) {
        // Runtime checks needed!
        assert!(self.is_connected && self.is_authenticated);
    }
}

// Nothing prevents this:
let mut conn = Connection { is_connected: false, is_authenticated: false };
conn.send_data("secret"); // Runtime panic!
```

### Solution: Typestate Pattern

```rust
// ✅ Compile-time state enforcement

// Define states as types
pub struct Disconnected;
pub struct Connected;
pub struct Authenticated;

// Connection parameterized by state
pub struct Connection<State> {
    addr: String,
    _state: std::marker::PhantomData<State>,
}

// Only disconnected connections can be created
impl Connection<Disconnected> {
    pub fn new(addr: String) -> Self {
        Self {
            addr,
            _state: std::marker::PhantomData,
        }
    }

    // Transition: Disconnected -> Connected
    pub fn connect(self) -> Connection<Connected> {
        println!("Connecting to {}", self.addr);
        Connection {
            addr: self.addr,
            _state: std::marker::PhantomData,
        }
    }
}

// Only connected connections can authenticate
impl Connection<Connected> {
    // Transition: Connected -> Authenticated
    pub fn authenticate(self, password: &str) -> Connection<Authenticated> {
        println!("Authenticating...");
        Connection {
            addr: self.addr,
            _state: std::marker::PhantomData,
        }
    }
}

// Only authenticated connections can send data
impl Connection<Authenticated> {
    pub fn send_data(&self, data: &str) {
        // No runtime checks needed - type system guarantees state!
        println!("Sending: {}", data);
    }

    pub fn disconnect(self) -> Connection<Disconnected> {
        println!("Disconnecting...");
        Connection {
            addr: self.addr,
            _state: std::marker::PhantomData,
        }
    }
}

// Usage
let conn = Connection::new("localhost:8080".to_string());
let conn = conn.connect();
let conn = conn.authenticate("password");
conn.send_data("secret data"); // ✅ Compiles

// Won't compile - must follow state transitions:
// let conn = Connection::new("localhost".to_string());
// conn.send_data("data"); // ❌ Type error!
```

### Typestate with Builder Pattern

```rust
pub struct RequestBuilder<Method, Body> {
    url: String,
    _method: std::marker::PhantomData<Method>,
    _body: std::marker::PhantomData<Body>,
}

// States
pub struct NoMethod;
pub struct Get;
pub struct Post;
pub struct NoBody;
pub struct HasBody(String);

impl RequestBuilder<NoMethod, NoBody> {
    pub fn new(url: String) -> Self {
        Self {
            url,
            _method: std::marker::PhantomData,
            _body: std::marker::PhantomData,
        }
    }

    pub fn get(self) -> RequestBuilder<Get, NoBody> {
        RequestBuilder {
            url: self.url,
            _method: std::marker::PhantomData,
            _body: std::marker::PhantomData,
        }
    }

    pub fn post(self) -> RequestBuilder<Post, NoBody> {
        RequestBuilder {
            url: self.url,
            _method: std::marker::PhantomData,
            _body: std::marker::PhantomData,
        }
    }
}

// GET requests can be sent without a body
impl RequestBuilder<Get, NoBody> {
    pub async fn send(self) -> Result<Response, Error> {
        // Send GET request
        todo!()
    }
}

// POST requests require a body
impl RequestBuilder<Post, NoBody> {
    pub fn body(self, body: String) -> RequestBuilder<Post, HasBody> {
        RequestBuilder {
 

Related in Design