Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pattern 15: Type State Enforcement

Context

An API has a prescribed call order (Arrange → Act → Assert) or requires configuration before use.

Problem

Runtime enforcement of order relies on documentation and can be bypassed, leading to inconsistent state and test flakiness.

Solution

Model the phases as distinct types and use PhantomData to encode the current phase. Methods consume self and return the next state, making it impossible to call methods out of order. Chicago TDD Tools uses this to enforce AAA semantics internally.

Forces

  • Safety vs. ergonomic: type transitions should read naturally without verbose syntax
  • Flexibility vs. constraints: provide escape hatches only when absolutely necessary
  • Zero-cost vs. clarity: type state should erase at compile time

Examples

#![allow(unused)]
fn main() {
pub struct TestState<Phase> {
    context: Context,
    _phase: PhantomData<Phase>,
}

impl TestState<Arrange> {
    pub fn act(self) -> TestState<Act> { /* ... */ }
}
}
  • Pattern 1: AAA Pattern
  • Pattern 11: Zero-Cost Abstractions
  • Pattern 14: Compile-Time Validation