Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Паттерн typestate: пример

Паттерн typestate кодирует часть состояния значения во время выполнения в его тип. Это позволяет предотвращать недопустимые или неприменимые операции на этапе компиляции.

// Copyright 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", "Алиса")
        .finish_struct();

    println!("{}", serializer.finish());
}

Схема использования Serializer:

SerializerSerializeStructserializestructserializefieldfinishstructfinish
  • Этот пример вдохновлён трейтом Serializer из Serde. Serde использует typestate внутри себя, чтобы сериализация следовала допустимой структуре. Подробнее см.: https://serde.rs/impl-serializer.html

  • Ключевая идея typestate в том, что переходы между состояниями происходят путём потребления одного значения и создания нового. На каждом шаге доступны только операции, допустимые для этого состояния.

  • В этом примере:

    • Начинаем с Serializer, который позволяет только начать сериализацию структуры.

    • После вызова .serialize_struct(...) владение переходит к значению SerializeStruct. С этого момента мы можем вызывать только методы, связанные с сериализацией полей структуры.

    • Исходный Serializer больше недоступен — это не позволяет смешивать режимы (например, начать другую структуру внутри структуры) или вызвать finish() слишком рано.

    • Только после вызова .finish_struct() мы снова получаем Serializer. На этом этапе вывод можно финализировать или переиспользовать.

  • Если мы забудем вызвать finish_struct() и отбросим SerializeStruct раньше времени, Serializer тоже будет освобождён. Это гарантирует, что неполный вывод не попадёт в систему.

  • Для сравнения: если бы мы реализовали всё непосредственно в Serializer, как показано на предыдущем слайде, ничто не помешало бы пропустить важные шаги или смешать потоки сериализации.