Type state pattern for compile-time state machines

Marcus Chen Jan 2026
1 tab
struct Locked;
struct Unlocked;

struct Door<State> {
    _state: std::marker::PhantomData<State>,
}

impl Door<Locked> {
    fn unlock(self) -> Door<Unlocked> {
        println!("Door unlocked");
        Door { _state: std::marker::PhantomData }
    }
}

impl Door<Unlocked> {
    fn open(self) {
        println!("Door opened");
    }
}

fn main() {
    let door = Door::<Locked> { _state: std::marker::PhantomData };
    let door = door.unlock();
    door.open();
}
1 file · rust Explain with highlit

The typestate pattern uses Rust's type system to encode state machines, making invalid states unrepresentable. Each state is a separate type, and transitions consume self and return a new state. The compiler prevents calling methods that aren't valid for the current state. I use this for protocols, workflows, and resource initialization. For example, a Connection can be Closed, Open, or Authenticated, and only Authenticated can send messages. The pattern is zero-cost: states are phantom types that disappear at runtime. It's more verbose than runtime state machines, but it catches logic errors at compile time. This is especially valuable for security-critical code where state transitions must be enforced.