Search code examples
rustsyntaxstate-machine

State machine in rust?


In the C family of languages I usually implement a state machine as a series of if else statements and enums, where if statements check in which state the machine is and the bodies execute state transitions, for example this:

if(current_left_state == GLFW_PRESS && !left_pressed)
{
    left_pressed = true;
    return MouseInputState::LEFT_DOWN;
}
if(current_left_state == GLFW_PRESS && left_pressed)
{
    left_pressed = true;
    return MouseInputState::LEFT_DRAG;
}
if(current_left_state == GLFW_RELEASE && left_pressed)
{
    left_pressed = false;
    return MouseInputState::LEFT_UP;
}
if(current_right_state == GLFW_PRESS && !right_pressed)
{
    right_pressed = true;
    return MouseInputState::RIGHT_DOWN;
}

Rust has a lot of idiomatic sugar which is nice, I was wondering if there is a way to use rust's syntactic sugar to make cleaner state machines.

Like, there has got to be a better way than this:

MouseState::NoAction =>
{
    if *button == glfw::MouseButtonLeft && *action == glfw::Action::Press
    {
        return MouseState::LeftDown;
    }
    if *button == glfw::MouseButtonRight && *action == glfw::Action::Press
    {
        return MouseState::RightDown;
    }
    return MouseState::NoAction;
}
MouseState::LeftDown =>
{
    if *button == glfw::MouseButtonLeft && *action == glfw::Action::Release
    {
        return MouseState::LeftUp;
    }
    return MouseState::LeftDrag;
}
MouseState::LeftDrag =>
{
    if *button == glfw::MouseButtonLeft && *action == glfw::Action::Release
    {
        return MouseState::LeftUp;
    }
    return MouseState::LeftDrag;
}
MouseState::LeftUp =>
{
    if *button == glfw::MouseButtonLeft && *action == glfw::Action::Press
    {
        return MouseState::LeftUp;
    }
    return MouseState::NoAction;
}

Solution

  • For really clean state machines, you can match on tuples:

    let next_state = match (current_state, button, action) => {
        (MouseState::NoAction, glfw::MouseButtonLeft, glfw::Action::Press) => MouseState::LeftDown,
        (MouseState::NoAction, glfw::MouseButtonRight, glfw::Action::Press) => MouseState::RightDown,
        (MouseState::NoAction, _, _) => MouseState::RightDown,
        (MouseState::LeftDown, glfw::MouseButtonLeft, glfw::Action::Release) => MouseState::LeftUp,
        (MouseState::LeftDown, _, _) => MouseState::LeftDrag,
        (MouseState::LeftDrag, glfw::MouseButtonLeft, glfw::Action::Release) => MouseState::LeftUp,
        (MouseState::LeftDrag, _, _) => MouseState::LeftDrag,
        (MouseState::LeftUp, glfw::MouseButtonLeft, glfw::Action::Press) => MouseState::LeftUp,
        (MouseState::LeftUp, _, _) => MouseState::NoAction,
        _ => MouseState::NoAction,
    }
    

    Depending on your preference, you can consolidate the branches with the same result using the | pattern.