PluginBench
Skill
Pass
Audit score 90

m05-type-driven

actionbook/rust-skills

Use Rust's type system to make invalid states unrepresentable at compile time.

What is m05-type-driven?

Type-driven design leverages Rust's type system to encode invariants and prevent invalid states before runtime. Use this when you need to validate data, manage state transitions, or add semantic meaning to primitives—asking "can the compiler catch this?" before reaching for runtime checks.

  • Encode invariants as types (newtype, type state, marker traits) to prevent invalid states
  • Use PhantomData to track type parameters for variance and lifetime control without runtime cost
  • Implement builder patterns for gradual, validated construction of complex types
  • Create sealed traits to restrict implementations to a closed set
  • Design state machines where invalid transitions are compile-time errors, not runtime panics

How to install m05-type-driven

npx skills add https://github.com/actionbook/rust-skills --skill m05-type-driven
Claude Code
Cursor
Windsurf
Cline

How to use m05-type-driven

  1. 1.Identify where runtime validation or boolean flags are used in your code
  2. 2.Ask: can the compiler catch this error instead? Can invalid states be unrepresentable?
  3. 3.Choose the appropriate pattern: newtype for primitives, type state for state machines, marker traits for capabilities, builder for gradual construction
  4. 4.Implement validation at construction time (e.g., Email::new) and trust the type thereafter
  5. 5.Use PhantomData when you need to track type parameters without runtime overhead

Use cases

Good for
  • Wrapping primitives with semantic meaning (e.g., UserId, Email) to prevent type confusion
  • Building connection or session types that enforce valid state sequences (Disconnected → Connected → Authenticated)
  • Preventing invalid configurations by making them unrepresentable in the type system
  • Validating data once at construction and trusting it forever thereafter
  • Implementing capability-based security where marker traits prove preconditions
Who it's for
  • Rust developers designing APIs or domain models
  • Teams building state machines or protocol implementations
  • Anyone replacing runtime validation with compile-time guarantees
  • Library authors enforcing invariants through types

m05-type-driven FAQ

When should I use newtype vs. type state?

Use newtype for wrapping primitives with semantic meaning (UserId, Email). Use type state when you need to encode state transitions and prevent invalid operation sequences.

What's the difference between a marker trait and PhantomData?

PhantomData is a zero-sized type that tracks type parameters for variance and lifetime control. A marker trait is a capability flag that proves a type has been validated or has certain properties.

How do I validate data once and trust it forever?

Create a newtype with a private constructor and a public `new()` method that validates. Once constructed, the type guarantees the invariant holds.

Can type-driven design replace all runtime validation?

No. Use type-driven design for invariants the compiler can check (state, semantic meaning, ranges). Use Result for errors that depend on external input or runtime conditions.

What's a sealed trait and when do I need it?

A sealed trait restricts implementations to a closed set by making the trait private and only implementing it for specific types. Use it when you want to prevent external crates from implementing a trait.

Full instructions (SKILL.md)

Source of truth, from actionbook/rust-skills.


name: m05-type-driven description: "CRITICAL: Use for type-driven design. Triggers: type state, PhantomData, newtype, marker trait, builder pattern, make invalid states unrepresentable, compile-time validation, sealed trait, ZST, 类型状态, 新类型模式, 类型驱动设计" user-invocable: false

Type-Driven Design

Layer 1: Language Mechanics

Core Question

How can the type system prevent invalid states?

Before reaching for runtime checks:

  • Can the compiler catch this error?
  • Can invalid states be unrepresentable?
  • Can the type encode the invariant?

Error → Design Question

PatternDon't Just SayAsk Instead
Primitive obsession"It's just a string"What does this value represent?
Boolean flags"Add an is_valid flag"Can states be types?
Optional everywhere"Check for None"Is absence really possible?
Validation at runtime"Return Err if invalid"Can we validate at construction?

Thinking Prompt

Before adding runtime validation:

  1. Can the type encode the constraint?

    • Numeric range → bounded types or newtypes
    • Valid states → type state pattern
    • Semantic meaning → newtype
  2. When is validation possible?

    • At construction → validated newtype
    • At state transition → type state
    • Only at runtime → Result with clear error
  3. Who needs to know the invariant?

    • Compiler → type-level encoding
    • API users → clear type signatures
    • Runtime only → documentation

Trace Up ↑

When type design is unclear:

"Need to validate email format"
    ↑ Ask: Is this a domain value object?
    ↑ Check: m09-domain (Email as Value Object)
    ↑ Check: domain-* (validation requirements)
SituationTrace ToQuestion
What types to createm09-domainWhat's the domain model?
State machine designm09-domainWhat are valid transitions?
Marker trait usagem04-zero-costStatic or dynamic dispatch?

Trace Down ↓

From design to implementation:

"Need type-safe wrapper for primitives"
    ↓ Newtype: struct UserId(u64);

"Need compile-time state validation"
    ↓ Type State: Connection<Connected>

"Need to track phantom type parameters"
    ↓ PhantomData: PhantomData<T>

"Need capability markers"
    ↓ Marker Trait: trait Validated {}

"Need gradual construction"
    ↓ Builder: Builder::new().field(x).build()

Quick Reference

PatternPurposeExample
NewtypeType safetystruct UserId(u64);
Type StateState machineConnection<Connected>
PhantomDataVariance/lifetimePhantomData<&'a T>
Marker TraitCapability flagtrait Validated {}
BuilderGradual constructionBuilder::new().name("x").build()
Sealed TraitPrevent external implmod private { pub trait Sealed {} }

Pattern Examples

Newtype

struct Email(String);  // Not just any string

impl Email {
    pub fn new(s: &str) -> Result<Self, ValidationError> {
        // Validate once, trust forever
        validate_email(s)?;
        Ok(Self(s.to_string()))
    }
}

Type State

struct Connection<State>(TcpStream, PhantomData<State>);

struct Disconnected;
struct Connected;
struct Authenticated;

impl Connection<Disconnected> {
    fn connect(self) -> Connection<Connected> { ... }
}

impl Connection<Connected> {
    fn authenticate(self) -> Connection<Authenticated> { ... }
}

Decision Guide

NeedPattern
Type safety for primitivesNewtype
Compile-time state validationType State
Lifetime/variance markersPhantomData
Capability flagsMarker Trait
Gradual constructionBuilder
Closed set of implsSealed Trait
Zero-sized type markerZST struct

Anti-Patterns

Anti-PatternWhy BadBetter
Boolean flags for statesRuntime errorsType state
String for semantic typesNo type safetyNewtype
Option for uninitializedUnclear invariantBuilder
Public fields with invariantsInvariant violationPrivate + validated new()

Related Skills

WhenSee
Domain modelingm09-domain
Trait designm04-zero-cost
Error handling in constructorsm06-error-handling
Anti-patternsm15-anti-pattern