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

Программирование с типовыми состояниями

Концепция [типовых состояний] описывает кодирование информации о текущем состоянии объекта в тип этого объекта. Хотя это может звучать немного загадочно, если вы использовали шаблон Builder в Rust, вы уже начали использовать программирование с типовыми состояниями!

pub mod foo_module {
    #[derive(Debug)]
    pub struct Foo {
        inner: u32,
    }

    pub struct FooBuilder {
        a: u32,
        b: u32,
    }

    impl FooBuilder {
        pub fn new(starter: u32) -> Self {
            Self {
                a: starter,
                b: starter,
            }
        }

        pub fn double_a(self) -> Self {
            Self {
                a: self.a * 2,
                b: self.b,
            }
        }

        pub fn into_foo(self) -> Foo {
            Foo {
                inner: self.a + self.b,
            }
        }
    }
}

fn main() {
    let x = foo_module::FooBuilder::new(10)
        .double_a()
        .into_foo();

    println!("{:#?}", x);
}

В этом примере нет прямого способа создать объект Foo. Мы должны создать FooBuilder и правильно его инициализировать, прежде чем сможем получить желаемый объект Foo.

Этот минимальный пример кодирует два состояния:

  • FooBuilder, который представляет состояние "неконфигурировано" или "конфигурация в процессе".
  • Foo, который представляет состояние "сконфигурировано" или "готово к использованию".

Сильная типизация

Поскольку Rust имеет [сильную систему типов], нет простого способа магически создать экземпляр Foo или превратить FooBuilder в Foo без вызова метода into_foo(). Кроме того, вызов метода into_foo() потребляет исходную структуру FooBuilder, что означает, что ее нельзя повторно использовать без создания нового экземпляра.

Это позволяет нам представлять состояния нашей системы как типы и включать необходимые действия для переходов между состояниями в методы, которые обменивают один тип на другой. Создавая FooBuilder и обменивая его на объект Foo, мы проходим через шаги простого конечного автомата.