При проектировании конечных автоматов в Rust хранение информации о текущем состоянии системы очень часто производится в объекте типа соответствующего его состоянию. При этом изменение состояния системы вызывает создание экземпляра другого типа (соответствующего её состоянию).
Выбор такого подхода в Rust связан со следующими особенностями:
Rust использует концепцию анализа Typestate
Используется строгая типизация данных, нет средств для автоматического создания и хранения объектов без инициализации всех значений для описанных полей данных. Для инициализации “сложных” объектов часто применяется шаблон проектирования Строитель (Builder)
Rust не предоставляет средств для автоматическ��й трансформации типов. Для перевода экземпляра данных одного типа в другой используется вызов соответствующих методов типа (например
into_f32())Язык Rust, избегая состояния гонки данных, повсеместно использует понятия владения и заимствования. Для перевода экземпляра данных одного типа в другой данные либо меняют владельца (и не доступны в первичном экземпляре после этого), либо в памяти должна быть размещена копия этих данных. Заимствование значения из исходного экземпляра в данном случае создаёт больше проблем.
Простейший пример построения конечного автомата имеющего два состояния:
состояние с не подготовленными данными будем хранить в типе
FooInit(реализует шаблон проектирования Строитель);состояние с данными готовыми к использованию будем хранить в типе
FooReady.
Реализация шаблона с однократным использованием структуры данных FooInit (созданием нового экземпляра на каждой итерации подготовки данных):
pub mod foo_system{
// Структура данных для работы с готовыми данными
#[derive(Debug)]
pub struct FooReady {
c: u32,
}
// Структура данных для подготовки данных по шаблону Строитель
pub struct FooInit {
a: u32,
b: u32,
}
// Реализация методов для шаблона Строитель
impl FooInit {
// Конструктор объекта FooInit с данными по умолчанию
pub fn new() -> Self {
Self {
a: 0,
b: 0,
}
}
// Подготовка данных в поле 'a' FooInit
pub fn set_a(self, a: u32) -> Self {
// Создаём новую структуру данных
Self {
a,
..self // заполняем другие поля из исходного экземпляра
}
}
// Подготовка данных в поле 'b' FooInit
pub fn set_b(self, b: u32) -> Self {
// Создаём новую структуру данных
Self {
b,
..self // заполняем другие поля из исходного экземпляра
}
}
// Смена состояния FooInit -> FooReady
pub fn into_foo(self) -> FooReady {
FooReady {
c: self.a + self.b,
}
}
}
}
fn main() {
// Создаём конечный автомат в состоянии FooInit
let foo = foo_system::FooInit::new()
// Подготавливаем данные в поле 'a'
.set_a(1)
// Подготавливаем данные в поле 'b'
.set_b(2)
// Переводим систему в состояние FooReady
.into_foo();
// Работаем с системой в состоянии FooReady
println!("{:#?}", foo);
}Реализация шаблона с повторным использованием структуры данных FooInit (изменением данных в исходной структуре на каждой итерации подготовки данных):
pub mod foo_system{
// Структура данных для работы с готовыми данными
#[derive(Debug)]
pub struct FooReady {
c: u32,
}
// Структура данных для подготовки данных по шаблону Строитель
pub struct FooInit {
a: u32,
b: u32,
}
// Реализация методов для шаблона Строитель
impl FooInit {
// Конструктор объекта FooInit с данными по умолчанию
pub fn new() -> Self {
Self {
a: 0,
b: 0,
}
}
// Подготовка данных в поле 'a' FooInit
pub fn set_a(&mut self, a: u32) -> &Self {
self.a = a; // меняем данные в структуре
self
}
// Подготовка данных в поле 'b' FooInit
pub fn set_b(&mut self, b: u32) -> &Self {
self.b = b; // меняем данные в структуре
self
}
// Смена состояния FooInit -> FooReady
pub fn into_foo(&self) -> FooReady {
FooReady {
c: self.a + self.b,
}
}
}
}
fn main() {
// Создаём конечный автомат в состоянии FooInit
let mut foo = foo_system::FooInit::new();
// Подготавливаем данные в поле 'a'
foo.set_a(1);
// Подготавливаем данные в поле 'b'
foo.set_b(2);
// Переводим систему в состояние FooReady
let foo = foo.into_foo();
// Работаем с системой в состоянии FooReady
println!("{:#?}", foo);
}В обоих вариантах реализации результатом исполнения будет вывод в консоль:
FooReady {
c: 3,
}