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: проблема

Как гарантировать, что над значением допускаются только операции, допустимые для его текущего состояния?

// 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());
}
This slide and its sub-slides should take about 65 minutes.
  • Этот Serializer предназначен для записи структурированного значения.

  • Однако в этом примере мы забыли вызвать serialize_struct_end() перед finish(). В результате сериализованный вывод оказывается неполным или синтаксически некорректным.

  • Один из способов исправить это — вручную отслеживать внутреннее состояние и возвращать Result из методов вроде serialize_struct_field() или finish(), если текущее состояние недопустимо.

  • Однако у этого подхода есть недостатки:

    • При реализации легко допустить ошибку. Система типов Rust не может помочь обеспечить корректность переходов между состояниями.

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

  • Более удачное решение — смоделировать допустимые переходы между состояниями прямо в системе типов.

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