Project 5: Type-State Builder Pattern (Make Invalid States Unrepresentable)

An HTTP request builder where the type system ensures you can’t send a request without setting required fields—illegal states are compile-time errors, not runtime exceptions.

Quick Reference

Attribute Value
Primary Language Rust
Alternative Languages Haskell (similar type system power)
Difficulty Level 2: Intermediate
Time Estimate 1 week
Knowledge Area Type System / Generics / API Design
Tooling Rust type system, PhantomData
Prerequisites Project 1 completed, basic generics understanding

What You Will Build

An HTTP request builder where the type system ensures you can’t send a request without setting required fields—illegal states are compile-time errors, not runtime exceptions.

Why It Matters

This project builds core skills that appear repeatedly in real-world systems and tooling.

Core Challenges

  • Encoding states as zero-sized types → maps to PhantomData and marker traits
  • Transitioning between states via method chaining → maps to consuming self and returning new types
  • Making the API impossible to misuse → maps to compile-time invariant enforcement
  • Keeping the API ergonomic despite type complexity → maps to good generic design

Key Concepts

  • Type-state pattern: “Rust for Rustaceans” Chapter 3 - Jon Gjengset
  • PhantomData: “The Rust Programming Language” Chapter 19 - Steve Klabnik
  • Zero-sized types: “Programming Rust, 2nd Edition” Chapter 11 - Jim Blandy
  • Builder pattern in Rust: “Rust API Guidelines” - rust-lang.github.io

Real-World Outcome

// This COMPILES ✅
let response = HttpRequest::new()
    .method(Method::POST)          // State: HasMethod
    .url("https://api.example.com") // State: HasUrl
    .header("Content-Type", "application/json")
    .body(json!({"name": "Alice"})) // State: HasBody
    .send()                         // Only available when all required fields set!
    .await?;

// This DOES NOT COMPILE ❌
let response = HttpRequest::new()
    .url("https://api.example.com")
    .send()  // Error: `send` not found—method not set!
    .await?;

// Compiler error:
// error[E0599]: no method named `send` found for struct
//               `HttpRequest<NoMethod, HasUrl>` in the current scope
//    |
// 45 |     .send()
//    |      ^^^^ method not found in `HttpRequest<NoMethod, HasUrl>`
//    |
//    = note: `send` requires `HttpRequest<HasMethod, HasUrl>`

Implementation Guide

  1. Reproduce the simplest happy-path scenario.
  2. Build the smallest working version of the core feature.
  3. Add input validation and error handling.
  4. Add instrumentation/logging to confirm behavior.
  5. Refactor into clean modules with tests.

Milestones

  • Milestone 1: Minimal working program that runs end-to-end.
  • Milestone 2: Correct outputs for typical inputs.
  • Milestone 3: Robust handling of edge cases.
  • Milestone 4: Clean structure and documented usage.

Validation Checklist

  • Output matches the real-world outcome example
  • Handles invalid inputs safely
  • Provides clear errors and exit codes
  • Repeatable results across runs

References

  • Main guide: LEARN_RUST_DEEP_DIVE.md
  • “Rust for Rustaceans” by Jon Gjengset