При проектировании конечных автоматов в 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, }
ссылка на оригинал статьи https://habr.com/ru/post/725148/
Добавить комментарий