3 ms·
Just to add more options, if you are willing to eat an allocation per transition, you can achieve a better developer experience by using trait objects. You can
by dpbriggs 7y ago
Just to add more options, if you are willing to eat an allocation per transition, you can achieve a better developer experience by using trait objects.
You can define the trait:
trait State: std::fmt::Debug {
fn transition(&self) -> Option<Box<dyn State>>;
}
Then implement the trait for each struct, and transition:
#[derive(Debug)]
struct FirstState {
foo: u32,
}
impl State for FirstState {
fn transition(&self) -> Option<Box<dyn State>> {
println!("First State Hit {}", self.foo);
Some(Box::new(SecondState {
bar: "Hello".to_owned(),
})) // transition to second state
}
}
Can even make an Iterator:
struct StateIterator {
curr_state: Option<Box<dyn State>>,
}
impl StateIterator {
fn new(curr_state: Option<Box<dyn State>>) -> Self {
Self { curr_state }
}
}
impl Iterator for StateIterator {
type Item = Box<dyn State>;
fn next(&mut self) -> Option<Self::Item> {
let next_state = self
.curr_state
.as_ref()
.and_then(|state| state.transition());
std::mem::replace(&mut self.curr_state, next_state)
}
}
And use it:
fn main() {
let first_state = Box::new(FirstState { foo: 0 });
for state in StateIterator::new(Some(first_state)) {
println!("{:?}", state);
}
}
Which outputs:
~ state-machine git:(master) cargo run
First State Hit 0
FirstState { foo: 0 }
Second State Hit: Hello
SecondState { bar: "Hello" }
Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=534a16753f5001ae2e3f814dc24f540e https://play.rust-lang.org/?version=stable&mode=debug&editio...
You can work-around allocating-per-state by making a hacky enum with an array of pointers, assuming the states themselves are immutable, which gives me an idea for a library.