Typestate パターン: 例
typestate パターンは、値の実行時状態の一部をその型にエンコードするものです。 これにより、無効または適用できない操作をコンパイル時に防ぐことができます。
// 著作権 2025 Google LLC // SPDX-License-Identifier: Apache-2.0 use std::fmt::Write as _; #[derive(Default)] struct Serializer { output: String, } struct SerializeStruct { serializer: Serializer, } impl Serializer { fn serialize_struct(mut self, name: &str) -> SerializeStruct { writeln!(&mut self.output, "{name} {{").unwrap(); SerializeStruct { serializer: self } } fn finish(self) -> String { self.output } } impl SerializeStruct { fn serialize_field(mut self, key: &str, value: &str) -> Self { writeln!(&mut self.serializer.output, " {key}={value};").unwrap(); self } fn finish_struct(mut self) -> Serializer { self.serializer.output.push_str("}\n"); self.serializer } } fn main() { let serializer = Serializer::default() .serialize_struct("User") .serialize_field("id", "42") .serialize_field("name", "Alice") .finish_struct(); println!("{}", serializer.finish()); }
Serializer の使用フローチャート:
-
この例は Serde の
Serializertrait に着想を得ています。 Serde はシリアライズが妥当な構造に従うことを保証するために、内部で typestate を使用しています。 詳しくは次を参照してください: https://serde.rs/impl-serializer.html -
typestate の背後にある重要な考え方は、状態遷移が値を消費して新しい値を生成することで起こる、ということです。 各ステップでは、その状態に対して有効な操作だけが利用できます。
-
この例では:
-
Serializerから開始し、これは構造体のシリアライズを開始することだけを許可します。 -
.serialize_struct(...)を呼び出すと、所有権はSerializeStructの値へ移動します。 その時点からは、構造体フィールドのシリアライズに関連するメソッドしか呼び出せません。 -
元の
Serializerにはもうアクセスできません。これにより、モードの混在 (たとえば、構造体 の途中で別の 構造体 を開始すること)や、finish()を早すぎるタイミングで呼び出すことを防げます。 -
.finish_struct()を呼び出した後にのみ、Serializerを受け取れます。 その時点で、出力を確定したり再利用したりできます。
-
-
finish_struct()の呼び出しを忘れてSerializeStructを早めにドロップすると、Serializerも一緒にドロップされます。 これにより、不完全な出力がシステムに漏れ出すことを防げます。 -
対照的に、前のスライドで見たように、すべてを
Serializerに直接実装していた場合は、 誰かが重要な手順を飛ばしたり、シリアライズのフローを混在させたりすることを防ぐものは何もありません。