1

Apologies for the convoluted title!

I'm new to rust, and I'm trying to solve a very specific problem. None of the solutions I've tried so far seem to work nicely within the language bounds.

I have a struct that contains data (called Model), and it also contains a dynamic Box for a "strategy" struct. There are several different strategy structs, and they are identified by a common Trait (Strategy) that each contain a separate way of modifying the model through their "execute strategy" method. By doing this, the I can dynamically change which strategy is used by changing the model's strategy instance like a state-machine.

Each strategy is immutable, but they mutate the model's data. They do sometimes contain their own immutable data, so enums seemed like an awkward fit.

trait Strategy {
   fn execute_strategy(&self, model: &mut Model);
}

struct Strategy1;

impl Strategy for Strategy1 {
   fn execute_strategy(&self, model: &mut Model) {
      ...
   }
}

struct Strategy2;

impl Strategy for Strategy2 {
   fn execute_strategy(&self, model: &mut Model) {
      ...
   }
}

struct Model {
   strategy: Box<dyn Strategy>,
}

Now the place I'm running into issues is executing the strategy.

impl Model {
   fn run(&mut self) {
      (*self.strategy).execute_strategy(self); // WONT COMPILE
   }
}

I get why the above won't compile -- It sees that self is borrowed immutably and mutably. Is there an idiomatic solution for this? Each strategy works in a vacuum and only modifies the model. The strategies themselves are immutable, so it seems like the above would be safe to do.

Happy New Year, and thanks in advance for the help!

2
  • "strategy works in a vacuum and only modifies the model" However, your model owns the strategy, so there really isn't a guarantee of this. You could mutate or even drop the strategy within the (immutable) strategy, quite undefined behavior. The best way to proceed depends on what you're doing, but typically you want to split things up into distinct types. Commented Jan 1, 2022 at 3:07
  • @GManNickG That's very valid. Thanks for the answer! Commented Jan 1, 2022 at 6:04

1 Answer 1

2

I don't know what's the most idiomatic way to solve this, but I can think of a few. One method would be to remove strategy from self so it's not a member of self during the operation, then put it back afterwards. This seems like an anti-pattern since you can forget to put it back afterwards (especially if you forget to handle an error):

impl Model {
    fn run(&mut self) {
        let strategy = std::mem::replace(&mut self.strategy, Box::new(EmptyStrategy {}));
        strategy.execute_strategy(self);
        let _ = std::mem::replace(&mut self.strategy, strategy);
    }
}

Note that there's a better API if you use Option<Box<dyn Strategy>>: you can get (and erase) the member with self.strategy.take() and replace the member with self.strategy.replace() (or assignment). But that doesn't solve the safety issue.

If your struct has just 1-2 members, a better way would be for the strategy to operate on those members instead of the struct itself. You can destructure the struct and run the strategy as follows:

impl Model {
    fn run(&mut self) {
        let Model {
            data,
            data2,
            strategy,
        } = self;
        strategy.execute_strategy(data, data2);
    }
}

The most general purpose and safe solution is probably to store the strategy in a Rc or Arc, then make a new reference copy for executing the strategy:

struct Model {
    strategy: Arc<Box<dyn Strategy>>,
}

impl Model {
    fn run(&mut self) {
        self.strategy.clone().execute_strategy(self);
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.