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.<p>You can define the trait:<p><pre><code> trait State: std::fmt::Debug {
fn transition(&self) -> Option<Box<dyn State>>;
}
</code></pre>
Then implement the trait for each struct, and transition:<p><pre><code> #[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
}
}
</code></pre>
Can even make an Iterator:<p><pre><code> 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)
}
}
</code></pre>
And use it:<p><pre><code> fn main() {
let first_state = Box::new(FirstState { foo: 0 });
for state in StateIterator::new(Some(first_state)) {
println!("{:?}", state);
}
}
</code></pre>
Which outputs:<p><pre><code> ~ state-machine git:(master) cargo run
First State Hit 0
FirstState { foo: 0 }
Second State Hit: Hello
SecondState { bar: "Hello" }
</code></pre>
Playground: <a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=534a16753f5001ae2e3f814dc24f540e" rel="nofollow">https://play.rust-lang.org/?version=stable&mode=debug&editio...</a><p>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.