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