ruststrategy-patternownershipdynamic-dispatch

How can I write this Rust variation on strategy pattern in which a given struct owns a strategy that modifies the original struct?


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!


Solution

  • 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);
        }
    }