Serializer: complete implementation
Looking back at our original desired flow:
We can now see this reflected directly in the types of our serializer:
The code for the full implementation of the Serializer
and all its states can
be found in
this Rust playground.
-
This pattern isn’t a silver bullet. It still allows issues like:
- Empty or invalid property names (which can be fixed using the newtype pattern)
- Duplicate property names (which could be tracked in
Struct<S>
and handled viaResult
)
-
If validation failures occur, we can also change method signatures to return a
Result
, allowing recovery:#![allow(unused)] fn main() { struct PropertySerializeError<S> { kind: PropertyError, serializer: Serializer<Struct<S>>, } impl<S> Serializer<Struct<S>> { fn serialize_property( self, name: &str, ) -> Result<Serializer<Property<Struct<S>>>, PropertySerializeError<S>> { /* ... */ } } }
-
While this API is powerful, it’s not always ergonomic. Production serializers typically favor simpler APIs and reserve the typestate pattern for enforcing critical invariants.
-
One excellent real-world example is
rustls::ClientConfig
, which uses typestate with generics to guide the user through safe and correct configuration steps.