Паттерн 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", "Алиса");
// serializer.serialize_struct_end(); // ← Упс! Забыли
println!("{}", serializer.finish());
}
-
Этот
Serializerпредназначен для записи структурированного значения. -
Однако в этом примере мы забыли вызвать
serialize_struct_end()передfinish(). В результате сериализованный вывод оказывается неполным или синтаксически некорректным. -
Один из способов исправить это — вручную отслеживать внутреннее состояние и возвращать
Resultиз методов вродеserialize_struct_field()илиfinish(), если текущее состояние недопустимо. -
Однако у этого подхода есть недостатки:
-
При реализации легко допустить ошибку. Система типов Rust не может помочь обеспечить корректность переходов между состояниями.
-
Кроме того, это создаёт лишнюю нагрузку на пользователя, который вынужден обрабатывать значения
Resultдля операций, которые используются неправильно уже в исходном коде, а не во время выполнения.
-
-
Более удачное решение — смоделировать допустимые переходы между состояниями прямо в системе типов.
На следующем слайде мы применим паттерн typestate, чтобы обеспечить корректное использование на этапе компиляции и сделать невозможным вызов несовместимых методов или пропуск обязательного действия.