Typestateパターン: 問題
ある値に対して、その現在の状態に応じた有効な操作だけを許可するには、どうすればよいでしょうか?
// Copyright 2025 Google LLC // SPDX-License-Identifier: Apache-2.0 use std::fmt::Write as _; #[derive(Default)] struct Serializer { output: String, } impl Serializer { fn serialize_struct_start(&mut self, name: &str) { let _ = writeln!(&mut self.output, "{name} {{"); } fn serialize_struct_field(&mut self, key: &str, value: &str) { let _ = writeln!(&mut self.output, " {key}={value};"); } fn serialize_struct_end(&mut self) { self.output.push_str("}\n"); } fn finish(self) -> String { self.output } } fn main() { let mut serializer = Serializer::default(); serializer.serialize_struct_start("User"); serializer.serialize_struct_field("id", "42"); serializer.serialize_struct_field("name", "Alice"); // serializer.serialize_struct_end(); // ← しまった、忘れていた println!("{}", serializer.finish()); }
-
この
Serializerは、構造化された値を書き出すことを目的としています。 -
しかし、この例では
finish()の前にserialize_struct_end()を呼ぶのを忘れています。その結果、シリアライズされた出力は不完全になったり、構文的に不正になったりします。 -
これを修正する 1 つの方法は、内部状態を手動で追跡し、現在の状態が不正であれば
serialize_struct_field()やfinish()のようなメソッドからResultを返すことです。 -
しかし、これには欠点があります:
-
実装者にとって間違えやすい方法です。Rust の型システムは、状態遷移の正しさを強制する助けになりません。
-
また、実行時ではなくソースコード上で誤用されている操作に対してまで、ユーザーが
Result値を処理しなければならず、不要な負担も増えます。
-
-
よりよい解決策は、有効な状態遷移を型システム内に直接モデル化することです。
次のスライドでは、typestateパターン を適用してコンパイル時に正しい使用法を強制し、互換性のないメソッドを呼び出したり、必要な操作をし忘れたりすることを不可能にします。