Hacker News new | ask | show | jobs
by dpbriggs 2266 days 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&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.