Lifetimes getting in the way of returning state machine to most recent state

Viewed 56

Example code:

struct S1 {}
struct S2 {}
struct S3 {}

enum State {
    Zero,
    One(S1),
    Two(S1, S2),
    Three(S1, S2, S3),
}

struct Client {
    state: Option<State>,
}

impl Client {
    fn try_advancing(&mut self) {
        /* other code */
        if let Some(inner) = std::mem::replace(&mut self.state, None) {
            match inner {
                State::Zero => {
                    if predicate0() {
                        self.state = Some(State::One(S1 {}));
                    }
                    else {
                        // code won't compile as we
                        // no longer own inner after partial move
                        self.state = Some(inner);
                    }
                },
                State::One(s1) => {
                    if predicate1() {
                        self.state = Some(State::Two(s1, S2 {}));
                    }
                    else {
                        self.state = Some(inner);
                    }
                },
                State::Two(s1, s2) => {
                    if predicate2() {
                        self.state = Some(State::Three(s1, s2, S3 {}));
                    }
                    else {
                        self.state = Some(inner);
                    }
                },
                State::Three(..) => {
                    if predicate3() {
                        // something else happens now
                    }
                }
            }
        }
    }
}

I need to look at state to determine whether to move to the next transition, but if I borrow it, then I can't transition because I don't own the value.

As possible solutions, I could use

  1. smart pointers

  2. a second match statement (the first borrowing and determine if we need to transition, the second moving and doing the transition)

  3. "rebuilding" the enum as it was, i.e. self.state = Some(inner); becomes self.state = State::Zero;

  4. only borrow the contents of inner until I know I want to transfer ownership, then do an if let, e.g.

State::One(ref s1) => {
   if predicate1() {
      if let State::One(s1) = inner {
         self.state = Some(State::Two(s1, S2 {}));
  1. Derive Copy and Clone for the unit structs.

All of those solutions incur run-time costs and what I would consider inelegant code. If this were another language, I could write the algorithm the way I think it should be, but with Rust, lifetimes seem to be preventing me from doing so. If only there were a way to tell the Rust compiler "in this branch of the if statement, I want to rescind my partial move and move inner back to where it was." Or perhaps, "Yes, I was borrowing the data, but now I no longer need those references and want to move the data." Is there such a way? Or any better alternative I haven't considered?

1 Answers

You can move the ifs into the match arms, so you can get different bindings:

match inner {
    State::Zero if predicate0() => {
        self.state = Some(State::One(S1 {}));
    },
    State::Zero => {
        self.state = Some(inner);
    },
    State::One(s1) if predicate1() => {
        self.state = Some(State::Two(s1, S2 {}));
    },
    State::One(_) => {
        self.state = Some(inner);
    },
    State::Two(s1, s2) if predicate2() => {
        self.state = Some(State::Three(s1, s2, S3 {}));
    },
    State::Two(_, _) => {
        self.state = Some(inner);
    },
    // ... snip ...
}
Related