Skip to content

Archive

Typestate

1 articles
Rust 01 Sep 2026 3 min read

Use the Typestate Pattern to Make Invalid Rust States Unrepresentable

Many APIs have lifecycle rules: a connection must be opened before sending, a transaction must begin before committing, or a builder must receive required values before producing output. Runtime flags can enforce these rules, but Rust can sometimes encode them in types instead. The typestate pattern represents each valid state with a distinct type and makes transitions consume one state to produce another. Encode states as marker types use std::marker::PhantomData; struct Disconnected; struct Connected; struct Connection<State> { endpoint: String, _state: PhantomData<State>, } impl Connection<Disconnected> { fn new(endpoint: String) -> Self { Self { endpoint, _state: PhantomData } } fn connect(self) -> Connection<Connected> { Connection { endpoint: self.endpoint, _state: PhantomData, } } } impl Connection<Connected> { fn send(&self, payload: &[u8]) { println!("sending {} bytes", payload.len()); } } send does not exist for Connection<Disconnected>. Incorrect call ordering becomes a compile-time error instead of a branch in production.