За пределами простого 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) -> ???
}
Схема допустимых переходов:
-
Развивая предыдущий сериализатор, мы теперь хотим поддержать вложенные структуры и списки.
-
Однако это приводит одновременно к дублированию и структурной сложности.
-
Что ещё важнее, мы упираемся в ограничение системы типов: мы не можем аккуратно выразить, что должен возвращать
finish(), не дублируя варианты для каждого контекста вложенности (например, корень, структура, список). -
По схеме допустимых переходов можно заметить:
- Переходы рекурсивны
- Тип возвращаемого значения зависит от того, где появляется вложенная структура или список
- Каждому контексту нужен путь возврата к своему родителю
-
Если использовать только конкретные типы, это становится неуправляемым. Наш текущий подход приводит к взрыву количества типов и ручной обвязке.
-
В следующей главе мы увидим, как обобщения позволяют моделировать рекурсивные потоки с меньшим количеством шаблонного кода, сохраняя при этом проверку допустимых операций на этапе компиляции.