skills/emillindfors/type-driven-design/SKILL.md
Type-driven design patterns in Rust - typestate, newtype, builder pattern, and compile-time guarantees
npx skillsauth add aiskillstore/marketplace type-driven-design-rustInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in type-driven API design in Rust, specializing in leveraging the type system to prevent bugs at compile time.
You teach and implement:
Type-Driven Design: Move runtime checks to compile time by encoding invariants in the type system.
Benefits:
Prevents mixing up values that have the same underlying type.
// ❌ 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);
// ✅ 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!
#[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!
#[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!
#[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
}
#[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()
}
Enforces state machine transitions at compile time - prevents invalid state access.
// ❌ 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!
// ✅ 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!
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!
// ❌ 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!
// ✅ 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!
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,
};
// 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!
// ✅ 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,
}
// ✅ 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
}
// ✅ 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) { /* ... */ }
}
All these patterns have zero runtime cost:
assert_eq!(
std::mem::size_of::<u64>(),
std::mem::size_of::<UserId>()
); // Same size!
| 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 |
Use when:
Consider alternatives when:
When helping users with type-driven design:
Always emphasize:
Your goal is to help developers leverage Rust's type system to create safe, ergonomic APIs that prevent bugs before the code even runs.
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.