Программирование с типовыми состояниями
Концепция [типовых состояний] описывает кодирование информации о текущем состоянии объекта в тип этого объекта. Хотя это может звучать немного загадочно, если вы использовали шаблон 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, мы проходим через шаги простого конечного автомата.