M05 Type Driven

by actionbook5c40d3ad7851No license1.5K starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 6 weeks ago

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, 类型状态, 新类型模式, 类型驱动设计

Instructions onlySoftware Development
AI-generated overview

Guides type-driven design in Rust, using types to make invalid states unrepresentable.

What it does
This skill provides guidance on type-driven design, covering patterns such as newtypes, type state, PhantomData, marker traits, builders, sealed traits, and zero-sized types. It frames design as a question of how the type system can prevent invalid states, with error-to-design prompts, decision tables, and anti-pattern comparisons. It also points to related skills for domain modeling, trait design, error handling, and anti-patterns.
When to use it
Use it when designing types or APIs where compile-time validation is preferable to runtime checks, such as encoding invariants, modeling state machines, or wrapping primitives for type safety. It is also useful when deciding between newtypes, type state, builders, or marker traits.
Requirements
No tools, packages, or runtime beyond the agent; it is instructions only and ships no scripts.

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

rust
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

rust
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

Source and attribution

Source:actionbook/rust-skillsinskills/m05-type-drivenat commit5c40d3a

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal