Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Type Driven Design

ASecurity

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

416 stars
0 votes
0 copies
2 views
Added 2/7/2026
developmentrustgosqlgitapidatabasedocumentation

Works with

cliapi

Security Analysis

A100/100

Scanned 2/12/2026

Install to Claude Code

$npx -y skills add aiskillstore/marketplace --skill type-driven-design --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Type Driven Design?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Type Driven Design
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aiskillstore-type-driven-design/badge)](https://www.skillsdirectory.com/skills/aiskillstore-type-driven-design)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: type-driven-design-rust
description: Type-driven design patterns in Rust - typestate, newtype, builder pattern, and compile-time guarantees
---

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 {
            url: self.url,
            _method: std::marker::PhantomData,
            _body: std::marker::PhantomData,
        }
    }
}

// Only POST with body can be sent
impl RequestBuilder<Post, HasBody> {
    pub async fn send(self) -> Result<Response, Error> {
        // Send POST request with body
        todo!()
    }
}

// Usage
let request = RequestBuilder::new("https://api.example.com".to_string())
    .get()
    .send()
    .await?; // ✅ GET without body

let request = RequestBuilder::new("https://api.example.com".to_string())
    .post()
    .body("data".to_string())
    .send()
    .await?; // ✅ POST with body

// Won't compile:
// let request = RequestBuilder::new("url")
//     .post()
//     .send(); // ❌ POST requires body!
```

## Pattern 3: Builder Pattern with Typestate

### Standard Builder (Runtime Validation)

```rust
// ❌ Runtime validation required
pub struct Config {
    host: String,
    port: u16,
    timeout: u64,
}

pub struct ConfigBuilder {
    host: Option<String>,
    port: Option<u16>,
    timeout: Option<u64>,
}

impl ConfigBuilder {
    pub fn new() -> Self {
        Self {
            host: None,
            port: None,
            timeout: None,
        }
    }

    pub fn host(mut self, host: String) -> Self {
        self.host = Some(host);
        self
    }

    pub fn port(mut self, port: u16) -> Self {
        self.port = Some(port);
        self
    }

    pub fn build(self) -> Result<Config, String> {
        // Runtime validation
        Ok(Config {
            host: self.host.ok_or("host is required")?,
            port: self.port.ok_or("port is required")?,
            timeout: self.timeout.unwrap_or(30),
        })
    }
}

// Can forget required fields:
let config = ConfigBuilder::new().build(); // Runtime error!
```

### Typestate Builder (Compile-Time Validation)

```rust
// ✅ Compile-time validation

pub struct Config {
    host: String,
    port: u16,
    timeout: u64,
}

// State markers
pub struct NoHost;
pub struct HasHost;
pub struct NoPort;
pub struct HasPort;

pub struct ConfigBuilder<HostState, PortState> {
    host: Option<String>,
    port: Option<u16>,
    timeout: u64,
    _host_state: std::marker::PhantomData<HostState>,
    _port_state: std::marker::PhantomData<PortState>,
}

impl ConfigBuilder<NoHost, NoPort> {
    pub fn new() -> Self {
        Self {
            host: None,
            port: None,
            timeout: 30,
            _host_state: std::marker::PhantomData,
            _port_state: std::marker::PhantomData,
        }
    }
}

impl<PortState> ConfigBuilder<NoHost, PortState> {
    pub fn host(self, host: String) -> ConfigBuilder<HasHost, PortState> {
        ConfigBuilder {
            host: Some(host),
            port: self.port,
            timeout: self.timeout,
            _host_state: std::marker::PhantomData,
            _port_state: std::marker::PhantomData,
        }
    }
}

impl<HostState> ConfigBuilder<HostState, NoPort> {
    pub fn port(self, port: u16) -> ConfigBuilder<HostState, HasPort> {
        ConfigBuilder {
            host: self.host,
            port: Some(port),
            timeout: self.timeout,
            _host_state: std::marker::PhantomData,
            _port_state: std::marker::PhantomData,
        }
    }
}

// Optional fields available on all builders
impl<HostState, PortState> ConfigBuilder<HostState, PortState> {
    pub fn timeout(mut self, timeout: u64) -> Self {
        self.timeout = timeout;
        self
    }
}

// Only build when all required fields are set
impl ConfigBuilder<HasHost, HasPort> {
    pub fn build(self) -> Config {
        // No Result needed - all required fields guaranteed!
        Config {
            host: self.host.unwrap(),
            port: self.port.unwrap(),
            timeout: self.timeout,
        }
    }
}

// Usage
let config = ConfigBuilder::new()
    .host("localhost".to_string())
    .port(8080)
    .timeout(60)
    .build(); // ✅ Returns Config directly, no Result

// Won't compile - missing required fields:
// let config = ConfigBuilder::new().build(); // ❌ Type error!
// let config = ConfigBuilder::new().host("localhost").build(); // ❌ Missing port!
```

## Pattern 4: Phantom Types for Compile-Time Invariants

### Example: Type-Safe IDs

```rust
use std::marker::PhantomData;

// Generic ID type parameterized by what it identifies
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Id<T> {
    value: u64,
    _marker: PhantomData<T>,
}

impl<T> Id<T> {
    pub fn new(value: u64) -> Self {
        Self {
            value,
            _marker: PhantomData,
        }
    }

    pub fn value(&self) -> u64 {
        self.value
    }
}

// Domain types
pub struct User {
    id: Id<User>,
    name: String,
}

pub struct Order {
    id: Id<Order>,
    user_id: Id<User>, // Type-safe foreign key!
    total: f64,
}

fn get_user(id: Id<User>) -> User {
    // ...
}

fn get_order(id: Id<Order>) -> Order {
    // ...
}

// Usage
let user_id = Id::<User>::new(42);
let order_id = Id::<Order>::new(100);

let user = get_user(user_id); // ✅
// let user = get_user(order_id); // ❌ Type error!

// Type-safe foreign keys
let order = Order {
    id: order_id,
    user_id: user_id, // ✅ Type-safe relationship
    total: 99.99,
};
```

## Pattern 5: Session Types

### Example: Protocol Enforcement

```rust
// States
pub struct Init;
pub struct Authenticated;
pub struct InTransaction;

pub struct DatabaseSession<State> {
    connection: Connection,
    _state: PhantomData<State>,
}

impl DatabaseSession<Init> {
    pub fn new(connection: Connection) -> Self {
        Self {
            connection,
            _state: PhantomData,
        }
    }

    pub fn authenticate(
        self,
        credentials: &Credentials,
    ) -> Result<DatabaseSession<Authenticated>, Error> {
        // Perform authentication
        Ok(DatabaseSession {
            connection: self.connection,
            _state: PhantomData,
        })
    }
}

impl DatabaseSession<Authenticated> {
    pub fn begin_transaction(self) -> DatabaseSession<InTransaction> {
        // Begin transaction
        DatabaseSession {
            connection: self.connection,
            _state: PhantomData,
        }
    }

    pub fn query(&self, sql: &str) -> Result<ResultSet, Error> {
        // Execute query outside transaction
        todo!()
    }
}

impl DatabaseSession<InTransaction> {
    pub fn execute(&mut self, sql: &str) -> Result<(), Error> {
        // Execute within transaction
        todo!()
    }

    pub fn commit(self) -> DatabaseSession<Authenticated> {
        // Commit transaction
        DatabaseSession {
            connection: self.connection,
            _state: PhantomData,
        }
    }

    pub fn rollback(self) -> DatabaseSession<Authenticated> {
        // Rollback transaction
        DatabaseSession {
            connection: self.connection,
            _state: PhantomData,
        }
    }
}

// Usage enforces protocol
let session = DatabaseSession::new(connection);
let session = session.authenticate(&credentials)?;
let mut session = session.begin_transaction();
session.execute("INSERT INTO ...")?;
session.execute("UPDATE ...")?;
let session = session.commit();

// Won't compile - must authenticate first:
// let session = DatabaseSession::new(connection);
// session.begin_transaction(); // ❌ Type error!
```

## Best Practices

### 1. Use Newtypes for Domain Modeling

```rust
// ✅ Good - clear, type-safe domain model
pub struct CustomerId(Uuid);
pub struct ProductId(Uuid);
pub struct Price(Decimal);
pub struct Quantity(u32);

struct Order {
    customer_id: CustomerId,
    items: Vec<OrderItem>,
}

struct OrderItem {
    product_id: ProductId,
    quantity: Quantity,
    price: Price,
}
```

### 2. Encode Validation in Types

```rust
// ✅ Good - impossible to create invalid email
pub struct Email(String);

impl Email {
    pub fn new(s: String) -> Result<Self, ValidationError> {
        validate_email(&s)?;
        Ok(Self(s))
    }
}

// Once you have an Email, it's valid!
fn send_notification(to: Email) {
    // No validation needed
}
```

### 3. Use Typestate for State Machines

```rust
// ✅ Good - state transitions enforced at compile time
struct Workflow<State> {
    data: WorkflowData,
    _state: PhantomData<State>,
}

struct Draft;
struct UnderReview;
struct Approved;

impl Workflow<Draft> {
    pub fn submit_for_review(self) -> Workflow<UnderReview> { /* ... */ }
}

impl Workflow<UnderReview> {
    pub fn approve(self) -> Workflow<Approved> { /* ... */ }
    pub fn reject(self) -> Workflow<Draft> { /* ... */ }
}

impl Workflow<Approved> {
    pub fn publish(self) { /* ... */ }
}
```

### 4. Leverage Zero-Cost Abstractions

All these patterns have **zero runtime cost**:
- Newtypes compile to the inner type
- PhantomData has zero size
- Typestate transitions are optimized away

```rust
assert_eq!(
    std::mem::size_of::<u64>(),
    std::mem::size_of::<UserId>()
); // Same size!
```

## Common Patterns Summary

| Pattern | Use Case | Benefits |
|---------|----------|----------|
| Newtype | Prevent mixing similar types | Type safety, zero cost |
| Typestate | Enforce state machines | Compile-time correctness |
| Builder + Typestate | Required vs optional fields | No runtime validation |
| Phantom Types | Generic type safety | Parameterized safety |
| Session Types | Protocol enforcement | API misuse prevention |

## When to Use Type-Driven Design

**Use when:**
- Domain has clear invariants
- State transitions are well-defined
- Type errors are better than runtime errors
- API misuse should be prevented
- Documentation through types is valuable

**Consider alternatives when:**
- States are very dynamic
- Transitions are data-dependent
- Compile times become too long
- API complexity outweighs benefits

## Resources

- [Type-Driven API Design in Rust](https://willcrichton.net/rust-api-type-patterns/)
- [Rust Design Patterns](https://rust-unofficial.github.io/patterns/)
- [Typestate Pattern](https://cliffle.com/blog/rust-typestate/)
- [Phantom Types](https://doc.rust-lang.org/nomicon/phantom-data.html)

## Your Role

When helping users with type-driven design:

1. **Identify invariants** - What should never be violated?
2. **Model states** - What states exist? What transitions?
3. **Encode in types** - Make invalid states unrepresentable
4. **Provide examples** - Show before/after
5. **Explain trade-offs** - Complexity vs safety
6. **Test compile errors** - Show what doesn't compile

Always emphasize:
- **Type safety** - Catch bugs at compile time
- **Zero cost** - No runtime overhead
- **Self-documentation** - Types explain usage
- **API design** - Make misuse impossible

Your goal is to help developers leverage Rust's type system to create safe, ergonomic APIs that prevent bugs before the code even runs.

Attribution

aiskillstoreaiskillstore
View sourceMore from aiskillstore →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

284072 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2192 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →