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

Serializer: complete implementation

Looking back at our original desired flow:

structureserializerpropertylistString

We can now see this reflected directly in the types of our serializer:

Serializer<Root>Serializer<Struct<S>>Serializer<Property<S>>StringSerializer<List<S>>finishstructserializestructfinishlistfinishstructserializestringorstructserializepropertyfinishfinishstructserializelistserializelistorstringorfinishlist

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 via Result)
  • If validation failures occur, we can also change method signatures to return a Result, allowing recovery:

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    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.