単純な Typestate を超えて

可能な状態や遷移が数多くある、ますます複雑な構成フローを、互換性のない操作を防ぎつつどのように管理すればよいでしょうか?

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Serializer {/* [...] */}
struct SerializeStruct {/* [...] */}
struct SerializeStructProperty {/* [...] */}
struct SerializeList {/* [...] */}

impl Serializer {
    // TODO: 実装する
    //
    // fn serialize_struct(self, name: &str) -> SerializeStruct
    // fn finish(self) -> String
}

impl SerializeStruct {
    // TODO: 実装する
    //
    // fn serialize_property(mut self, name: &str) -> SerializeStructProperty

    // TODO:
    // この構造体はどのように完了すべきでしょうか? これは、その構造体がどこに現れるかによって異なります:
    // - ルートレベル: `Serializer` を返す
    // - 別の構造体内のプロパティとして: `SerializeStruct` を返す
    // - リスト内の値として: `SerializeList` を返す
    //
    // fn finish(self) -> ???
}

impl SerializeStructProperty {
    // TODO: 実装する
    //
    // fn serialize_string(self, value: &str) -> SerializeStruct
    // fn serialize_struct(self, name: &str) -> SerializeStruct
    // fn serialize_list(self) -> SerializeList
    // fn finish(self) -> SerializeStruct
}

impl SerializeList {
    // TODO: 実装する
    //
    // fn serialize_string(mut self, value: &str) -> Self
    // fn serialize_struct(mut self, value: &str) -> SerializeStruct
    // fn serialize_list(mut self) -> SerializeList

    // TODO:
    // `SerializeStruct::finish` と同様に、戻り値の型はネストに依存します。
    //
    // fn finish(mut self) -> ???
}

有効な遷移の図:

structureserializerpropertylistString
  • これまでのシリアライザーを土台として、今度は ネストした構造体リスト をサポートしたいと考えます。

  • しかし、これによって 重複構造的な複雑さ の両方が生じます。

  • さらに重大なのは、ここで 型システムの制約 に突き当たることです。ネストのコンテキストごと(例: ルート、構造体、リスト)にバリアントを複製しない限り、finish() が何を返すべきかをすっきり表現できません。

  • 有効な遷移の図から、次のことが分かります:

    • 遷移は再帰的である
    • 戻り値の型は、サブ構造やリストが どこに 現れるかに依存する
    • 各コンテキストは親へ戻るための経路を必要とする
  • 具体型だけでは、これは管理不能になります。現在のアプローチでは、型が爆発的に増え、手動でつなぎ込む作業も必要になります。

  • 次の章では、ジェネリクス によって、コンパイル時に有効な操作を強制しつつ、より少ないボイラープレートで再帰的なフローをモデル化できることを見ていきます。