Lifetime too long with trait objects (writing a state machine)

I am adapting some state machine code I found in this blog post using Box<dyn State<S>> to erase the state type. My problem: something in my trait definitions causes the borrow of the actually mutated state to be too long, but I am not seeing it.

Note: I know that strictly speaking I don't need any lifetimes here, but I want to be able to have state that contains borrows, e.g. struct Game<'a> { text: &'a str }.

error[E0499]: cannot borrow `game` as mutable more than once at a time
  --> src/main.rs:60:34
   |
60 |         state = match state.next(&mut game) {
   |                                  ^^^^^^^^^ `game` was mutably borrowed here in the previous iteration of the loop

Code: Rust Playground

struct Ping;
struct Pong;

#[derive(Debug)]
struct Game {
    how_many_ping: usize,
    how_many_pong: usize,
}

trait State<S> {
    fn next(self: Box<Self>, state: &mut S) -> Transition<S>;
}

impl State<Game> for Ping {
    fn next(self: Box<Self>, state: &mut Game) -> Transition<Game> {
        state.how_many_ping += 1;
        if state.how_many_ping > 100 {
            return Transition::Complete(());
        }
        Transition::next(self, Pong)
    }
}

impl State<Game> for Pong {
    fn next(self: Box<Self>, state: &mut Game) -> Transition<Game> {
        state.how_many_pong += 1;
        if state.how_many_pong > 100 {
            return Transition::Complete(());
        }
        Transition::next(self, Ping)
    }
}

enum Transition<'a, S> {
    Next(Box<dyn State<S> + 'a>),
    Complete(()),
}

impl<'a, S> Transition<'a, S> {
    fn next<'b, In, Out>(_: Box<In>, out: Out) -> Transition<'b, S>
    where
        In: State<S> + TransitionTo<Out>,
        Out: State<S> +'b,
    {
        Transition::Next(Box::new(out))
    }
}

trait TransitionTo<U> {}

impl TransitionTo<Ping> for Pong {}
impl TransitionTo<Pong> for Ping {}


fn main() {
    let mut game = Game { how_many_ping: 0, how_many_pong: 0 };
    let mut state: Box<dyn State<Game>> = Box::new(Ping);

    loop {
        state = match state.next(&mut game) {
            Transition::Next(state) => state,
            Transition::Complete(_) => break,
        }
    }

    println!("{:#?}", game);
}


The problem with the code above was that fn next had the lifetime ellided. Rewriting it using rust's lifetime rules it would look like this:

fn next(self: Box<Self>, state: &mut S) -> Transition<S>
--->
fn next<'a>(self: Box<Self>, state: &'a mut S) -> Transition<'a, S>

and so now we see that this binds the lifetime 'a of the borrowed state to that of the state node.

This is the fix:

fn next<'a, 'b>(Self: Box<Self>, state: &'a mut S) -> Transition<'b, S>
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.