user19831231
user19831231

Reputation: 1

how do i pass around a mutable object in rust

I have some Rust trait for some effects that manipulate the game state by applying them

pub struct GameState {some_number : usize}
impl GameState {
  pub fn apply(self, e: &dyn Effect) -> GameState {
     // <do some accounting>
     return e.apply(self);
}

pub trait Effect {    
  fn apply(self, state: GameState) -> GameState;
}

in one of the Effects I want to apply another one:

pub struct MyFirstEffect;
impl Effect for MyFirstEffect {
  fn apply(self, mut state: GameState) -> GameState{
    for state.some_number > 0 {        
      state = state.apply(&MyOtherEffect::new());
    }
    return state;
  }
}
pub struct MyOtherEffect;
impl Effect for MyFirstEffect {
  fn apply(self, mut state: GameState) -> GameState{
    return GameState{state.some_number +1 }
  }
}

It is important for me, that Effect::apply(..) is only called in the GameState::apply(..) such that I can do some book keeping and maybe apply some other effects that have hooks on certain effects, but that is not the problem here.

My problem is that the borrow checker hates this. I first thought about this like passing a mutable reference around (instead of the "-> GameState") but it does not let me do that: In the loop it complains that the state was already borrowed. So I came up with this variant where I hand out an immutable GameState every time, which is crazy inefficient, but I thought that's the dumbest and safest way, but the borrow checker doesn't let me do that either because the lifetime of the GameState is not known. So I tried switching around the & and &mut but nothing is pleasing the borrow checker. Now I think that I should come up with some error messages that I get. But instead I just want to ask, how do I do such thing in general? Someone who is good in working with Rust should imho know that ad-hoc

Upvotes: 0

Views: 723

Answers (1)

Micron Mushroom
Micron Mushroom

Reputation: 97

At a glance the main problem you are having is probably something like: "use of moved value: 'state' value moved here, in previous iteration of loop". Though I can't know for certain because you haven't posted the error the borrow checker is giving you. Regardless if that were the case, the cause of the problem would be that Copy isn't implemented for GameState and you are moving it into the context of GameState::apply.

Problem one can be solved three ways:

  1. Implement copy for GameState. (This fixes the borrow checker issue but wouldn't achieve the behaviour you desire because state would remain unchanged by the end of MyFirstEffect::apply)
  2. Follow @cdhowie's suggestion which takes back the mutated state so that it can be mutated again in the next loop iteration. (This is the answer to how your question, but it's not the solution I would go with.)
  3. Change the apply functions to accept mutable references. (I would recommend this option because it avoids the issue of moving more and more data as the contents of GameState grows. All you need to move is a reference, which is of a fixed size regardless how big GameState is.)

Option three would look something like this:

pub struct GameState {some_condition: usize}
impl GameState {
    pub fn apply(&mut self, e: &dyn Effect) {
        // NOTE: for state.someCondition > 0 is incorrect for two reasons and
        // confusing for one. Incorrect because:
        // 1. Using `for` here is not consistent with rust's syntax. A while
        //   loop should be used instead.
        // 2. Rust variable names and struct fields are generally written in
        //    snake_case rather than camelCase.
        // 
        // And it's a little confusing because the `some_condition` variable is
        // not a condition, it's some value.
        while self.some_condition > 0 {
            e.apply_to(self);
        }
    }
}

pub trait Effect {
    // Consider changing `apply` -> `apply_to` because you are applying the
    // effect onto the state rather than the state onto the effect, and it keeps
    // the function distinct from `GameState::apply`.
    fn apply_to(&self, state: &mut GameState);
}

struct ZeroCondition;
impl Effect for ZeroCondition {
    fn apply_to(&self, state: &mut GameState) {
        state.some_condition -= 1;
    }
}

fn main() {
    let mut state = GameState {some_condition: 3};
    state.apply(&ZeroCondition);
}

Upvotes: 2

Related Questions